diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0371cd191b65fb2e6f7d35c4825e42c9e9c81f46..11cdd76478dcf6b1449bfe550bc90bf87c41b0ca 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,6 +32,18 @@ build_dev: - docker build --pull -t "$CI_REGISTRY_IMAGE:dev" --build-arg conf=dev . - docker push "$CI_REGISTRY_IMAGE:dev" +build_json_server: + image: docker:18.09 + services: + - docker:18.09-dind + stage: build + only: + - dev + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build --pull -t "$CI_REGISTRY_IMAGE:json_server" ./api/ + - docker push "$CI_REGISTRY_IMAGE:json_server" + deploy_dev: stage: deploy tags: diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4a8add9b9783d01d9cbd2bb165b0f8180966eeee --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,18 @@ +# Stage 0, based on Node.js +FROM node:12.16-slim + +WORKDIR /app + +# Copy the package.json file first in order to cache the modules +COPY . . + +RUN mkdir api +RUN cp routes.json ./api + +# Install npm dependencies +RUN npm install + +# expose port 3000 +EXPOSE 80 3000 + +CMD ["npm", "run", "api"] diff --git a/api/package.json b/api/package.json new file mode 100644 index 0000000000000000000000000000000000000000..a74b88aa616f75ce8ef255ac7f4d73addd098495 --- /dev/null +++ b/api/package.json @@ -0,0 +1,11 @@ +{ + "name": "pamn-mock-api", + "version": "0.0.1", + "scripts": { + "api": "json-server api/db.json --routes api/routes.json --no-cors=true" + }, + "private": true, + "dependencies": { + "json-server": "^0.16.2" + } +} diff --git a/karma.conf.js b/karma.conf.js index 813b2acf27a7877dc1f370594a4527d1192450fa..e0feaefc765ff9a114afb9babfec76106204a163 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -10,23 +10,41 @@ module.exports = function (config) { require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma') + require('@angular-devkit/build-angular/plugins/karma'), ], client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser + clearContext: false, // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, './coverage/pamn'), reports: ['html', 'lcovonly', 'text-summary'], - fixWebpackSourcePaths: true + fixWebpackSourcePaths: true, }, reporters: ['progress', 'kjhtml'], port: 9876, + proxies: { + '/api': { + target: 'http://localhost:3000', + }, + '/base-adresse/base-adresse-nationale/streets': { + target: 'https://passerelle.formulaireextranet.grandlyon.com', + changeOrigin: true, + }, + '/geocoding/photon/api': { + target: 'https://download.data.grandlyon.com', + changeOrigin: true, + }, + '/reverse': { + target: 'https://api-adresse.data.gouv.fr', + changeOrigin: true, + }, + }, + colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false, - restartOnFileChange: true + restartOnFileChange: true, }); }; diff --git a/package.json b/package.json index 4b09a880a8b22842e0d35675d0a97c84bb377251..8c71c182f7999d0f66f2979bf5d8a6284af992c4 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "json-server": "^0.16.2", "leaflet": "^1.7.1", "leaflet.locatecontrol": "^0.72.0", + "lodash": "^4.17.20", "luxon": "^1.25.0", "rxjs": "~6.6.0", "tslib": "^2.0.0", diff --git a/proxy.conf.json b/proxy.conf.json index 4cafc6d48237ca58e50c46d222b233618a624ec7..f6d20c2ac69c72bd5ebe74bfd45d949b48ea80c2 100644 --- a/proxy.conf.json +++ b/proxy.conf.json @@ -13,5 +13,11 @@ "secure": false, "changeOrigin": true, "logLevel": "info" + }, + "/reverse": { + "target": "https://api-adresse.data.gouv.fr", + "secure": false, + "changeOrigin": true, + "logLevel": "info" } } diff --git a/src/app/footer/footer.component.scss b/src/app/footer/footer.component.scss index 008df688f95f7029ed2f321c4d3606ec22b28c5a..b438f3aeab19b37195717fadedf03526461c46b1 100644 --- a/src/app/footer/footer.component.scss +++ b/src/app/footer/footer.component.scss @@ -9,14 +9,18 @@ display: flex; align-items: center !important; justify-content: space-between !important; - padding: 0px 4vw; - flex: 0 0 auto; + padding: 0px 22px; a { color: $white; - margin: 0 10px 0 12px; + margin: 0px 0px 0px 10px; &:hover { text-decoration: underline; } + &:not(:last-child)::after { + content: '•'; + margin: 0px 0px 0px 10px; + display: inline-block; + } } img { height: 37px; diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index 78509bdd99c991d973d2fb70cfa70001abf123cf..b83efd30176e7eebf8babdc858a66b31d2851063 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -1,20 +1,23 @@ <div fxLayout="row" fxLayout.lt-sm="column" class="header"> <div class="logo clickable"> - <div fxLayout="column"> - <span><span class="primary">╔╧╤</span> Médiation</span> - <span><span class="secondary">╚╪╧╤</span> Numérique</span> + <div fxLayout="row"> + <img class="logo-grand-lyon" src="/assets/logos/ram_logo.svg" alt /> + <div fxLayout="column" fxLayoutAlign="center"> + <p>Réseau des Acteurs de la Médiation</p> + <p>de la Métropole de Lyon</p> + </div> </div> </div> - <div fxLayout="row" class="right-header" fxLayoutGap="4vw"> - <div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="4vw"> - <a routerLink="/about" [routerLinkActive]="'active'" i18n>Qui sommes-nous ?</a> - <a routerLink="/resources" [routerLinkActive]="'active'" i18n>Ressources</a> - <a routerLink="/projects" [routerLinkActive]="'active'" i18n>Projets</a> - <a routerLink="/sturctures" [routerLinkActive]="'active'" class="highlight" i18n>Médiateurs numériques</a> - </div> - <div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="40px" class="grey-background"> - <a routerLink="/login" [routerLinkActive]="'active'" i18n>Se connecter</a> - <span class="clickable ico-mglass purple"></span> - </div> + <div fxLayout="row" class="right-header" fxLayoutAlign="center center" fxLayoutGap="3vw"> + <a routerLink="/home" [routerLinkActive]="'active'" i18n>Acteurs de la médiation</a> + <a routerLink="/resources" [routerLinkActive]="'active'" i18n>Ressources</a> + <a routerLink="/projects" [routerLinkActive]="'active'" i18n>Projets</a> + <a routerLink="/about" [routerLinkActive]="'active'" i18n>Qui sommes-nous ?</a> + <a routerLink="/login" [routerLinkActive]="'active'" i18n><span class="clickable ico-mglass purple"></span></a> + <a routerLink="/home" [routerLinkActive]="'active'" i18n + ><span class="ico-profile" fxLayout="column" fxLayoutAlign="center center"> + <span class="head"></span> + <span class="body"></span> </span + ></a> </div> </div> diff --git a/src/app/header/header.component.scss b/src/app/header/header.component.scss index 6dfdc3889c626451bc104d83813d68a6d914eae8..304251c5745605c06e538b8b9c0db0201197c6fb 100644 --- a/src/app/header/header.component.scss +++ b/src/app/header/header.component.scss @@ -9,37 +9,25 @@ height: $header-height; align-items: center; justify-content: space-between; - padding: 0px 0 0 4vw; text-align: center; - flex: 0 0 auto; + border-bottom: solid 1px $grey-4; + background-color: $white; + padding: 0 20px; a { + font-style: italic; &.active { - @include highlight; + font-weight: bold; } } .right-header { height: 100%; } - .grey-background { - height: 100%; - padding: 0 3vw; - background-color: $grey-2; - a { - text-decoration: underline; - } - } } .logo { - @include cn-regular-28; + @include cn-regular-14; text-align: initial; - .primary { - color: $primary-color; - } - .secondary { - color: $secondary-color; - } - @media #{$tablet} { - @include cn-regular-16; + p { + margin: 0; } } diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 930a43cf2af90799aaa00f29bba0d036c3236c4f..7b43d38870e5fd73af7b8153c4b498fc324f9ba0 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -1,9 +1,10 @@ -<div fxLayout="row" style="height: 100%"> +<div fxLayout="row" class="content-container"> <app-structure-list - (searchEvent)="fetchResults($event)" + (searchEvent)="getStructures($event)" [structureList]="structures" + [location]="currentLocation" class="left-pane" fxLayout="column" ></app-structure-list> - <app-map [structures]="structures" fxFlex="100"></app-map> + <app-map [structures]="structures" class="right-pane"></app-map> </div> diff --git a/src/app/home/home.component.scss b/src/app/home/home.component.scss index f2884d25f46208c83b984ac69b08d27b8b80ea73..e501173f8a4396e1be6c09814793874c8da26f6d 100644 --- a/src/app/home/home.component.scss +++ b/src/app/home/home.component.scss @@ -2,3 +2,11 @@ width: 640px; min-width: 640px; } +.right-pane { + width: 80%; + height: 80%; + padding: 0 40px 40px 40px; +} +.content-container { + height: 100%; +} diff --git a/src/app/home/home.component.spec.ts b/src/app/home/home.component.spec.ts index 8c0ce9e1243e588f86c013549b02032ace2a3b04..6aeea5f08a025669387b5c6e93fe67711ac7317d 100644 --- a/src/app/home/home.component.spec.ts +++ b/src/app/home/home.component.spec.ts @@ -7,15 +7,22 @@ import { HomeComponent } from './home.component'; describe('HomeComponent', () => { let component: HomeComponent; let fixture: ComponentFixture<HomeComponent>; + let originalTimeout; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [HomeComponent], - imports: [HttpClientTestingModule], + imports: [HttpClientModule], }).compileComponents(); }); + afterEach(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; + }); + beforeEach(() => { + originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; fixture = TestBed.createComponent(HomeComponent); component = fixture.componentInstance; fixture.detectChanges(); @@ -24,4 +31,38 @@ describe('HomeComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('getCoord(): should get coord', async () => { + await new Promise((resolve) => { + component.getCoord(21356).subscribe( + (val) => { + expect(val.geometry.getLat()).toEqual(69800); + expect(val.geometry.getLon).toEqual(69800); + resolve(); + }, + (err) => { + resolve(); + } + ); + }); + }); + + it('getAddress(): should getAddress', () => { + spyOn(navigator.geolocation, 'getCurrentPosition').and.callFake(() => { + const position = { + coords: { + accuracy: 1490, + altitude: null, + altitudeAccuracy: null, + heading: null, + latitude: 45.747404800000005, + longitude: 4.8529408, + speed: null, + }, + }; + return position; + }); + component.getLocation(); + expect(navigator.geolocation.getCurrentPosition).toHaveBeenCalled(); + }); }); diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 090ae1018adc283c418a068292517ce2a555db82..262d1cf2b96a775d0cb089269093e2aa0ed62be5 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -1,9 +1,14 @@ import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; import { mergeMap } from 'rxjs/operators'; +const { DateTime } = require('luxon'); +import * as _ from 'lodash'; + import { Structure } from '../models/structure.model'; import { StructureService } from '../services/structure-list.service'; import { Filter } from '../structure-list/models/filter.model'; -const { DateTime } = require('luxon'); +import { GeoJson } from '../map/models/geojson.model'; +import { GeojsonService } from '../services/geojson.service'; @Component({ selector: 'app-home', @@ -12,21 +17,76 @@ const { DateTime } = require('luxon'); }) export class HomeComponent implements OnInit { public structures: Structure[] = []; - constructor(private structureService: StructureService) {} + public geolocation = false; + public currentLocation: GeoJson; + constructor(private structureService: StructureService, private geoJsonService: GeojsonService) {} ngOnInit(): void { - this.structureService.getStructures(null).subscribe((structures) => { - this.structures = structures.map((structure) => - this.structureService.updateOpeningStructure(structure, DateTime.local()) - ); - }); + if (navigator.geolocation) { + this.getLocation(); + } + this.getStructures(null); } - public fetchResults(filters: Filter[]): void { + public getStructures(filters: Filter[]): void { this.structureService.getStructures(filters).subscribe((structures) => { - this.structures = structures.map((structure) => - this.structureService.updateOpeningStructure(structure, DateTime.local()) - ); + Promise.all( + structures.map((structure) => { + if (this.geolocation) { + return this.getStructurePosition(structure).then((val) => { + return this.structureService.updateOpeningStructure(val, DateTime.local()); + }); + } else { + return this.structureService.updateOpeningStructure(structure, DateTime.local()); + } + }) + ).then((structureList) => { + this.structures = _.sortBy(structureList, ['distance']); + }); + }); + } + + /** + * Get structures positions and add marker corresponding to those positons on the map + */ + private getStructurePosition(structure: Structure): Promise<Structure> { + return new Promise((resolve, reject) => { + this.getCoord(structure.voie).subscribe((coord: GeoJson) => { + structure.distance = this.geoJsonService.getDistance( + coord.geometry.getLon(), + coord.geometry.getLat(), + this.currentLocation.geometry.getLon(), + this.currentLocation.geometry.getLat(), + 'M' + ); + resolve(structure); + }); }); } + + /** + * Get coord with a street reference + * @param idVoie Street reference + */ + public getCoord(idVoie: number): Observable<GeoJson> { + return this.geoJsonService.getAddressByIdVoie(idVoie).pipe(mergeMap((res) => this.geoJsonService.getCoord(res))); + } + + public getLocation(): void { + navigator.geolocation.getCurrentPosition((position) => { + this.geolocation = true; + const longitude = position.coords.longitude; + const latitude = position.coords.latitude; + this.getAddress(longitude, latitude); + }); + } + + private getAddress(longitude: number, latitude: number): void { + this.geoJsonService.getAddressByCoord(longitude, latitude).subscribe( + (location) => { + this.currentLocation = location; + }, + (err) => console.error(err) + ); + } } diff --git a/src/app/map/components/map.component.html b/src/app/map/components/map.component.html index 4ed0d90d50bac4bf1baf0b64472986f93bc7d62a..4fc6326081f2e5319cdf04ebd9bf7cabbe78d282 100644 --- a/src/app/map/components/map.component.html +++ b/src/app/map/components/map.component.html @@ -1,2 +1,4 @@ -<div id="map" leaflet [leafletOptions]="mapOptions" (leafletMapReady)="onMapReady($event)"></div> -<leaflet-locate-control [map]="map" [options]="locateOptions"></leaflet-locate-control> +<div class="map-wrapper"> + <div id="map" class="body-wrap" leaflet [leafletOptions]="mapOptions" (leafletMapReady)="onMapReady($event)"></div> + <leaflet-locate-control [map]="map" [options]="locateOptions"></leaflet-locate-control> +</div> diff --git a/src/app/map/components/map.component.scss b/src/app/map/components/map.component.scss index 7c09074d9886cf24080ce0077526625ef2cb3f2b..d12f41e83db62e2bc3c6afdeee0bc21d2eba0a24 100644 --- a/src/app/map/components/map.component.scss +++ b/src/app/map/components/map.component.scss @@ -2,17 +2,23 @@ @import '../../../assets/scss/layout'; @import '../../../assets/scss/icons'; @import '../../../assets/scss/typography'; +@import '../../../assets/scss/shapes'; + +.map-wrapper { + border-radius: 6px; + @include background-hash; +} #map { - height: calc(100vh - #{$header-height} - #{$footer-height}); + height: calc(100vh - #{$header-height} - #{$footer-height} - 50px); width: 100%; + border: 10px solid white; + border-radius: 6px; } ::ng-deep .fa-map-marker { color: $black; position: absolute; - margin-left: 4px; - margin-top: 2px; width: 12px; height: 12px; border: solid 1px currentColor; diff --git a/src/app/map/components/map.component.ts b/src/app/map/components/map.component.ts index f63314cb98888b7b38cc9d8c1593fb6cec90894b..7330bf5fd46f44a2b25386e89a6e51be216573b7 100644 --- a/src/app/map/components/map.component.ts +++ b/src/app/map/components/map.component.ts @@ -4,7 +4,7 @@ import { Observable } from 'rxjs'; import { mergeMap } from 'rxjs/operators'; import { Structure } from '../../models/structure.model'; import { GeoJson } from '../models/geojson.model'; -import { GeojsonService } from '../services/geojson.service'; +import { GeojsonService } from '../../services/geojson.service'; import { MapService } from '../services/map.service'; @Component({ @@ -21,11 +21,9 @@ export class MapComponent implements OnChanges { public locateOptions = { flyTo: false, keepCurrentZoomLevel: false, - locateOptions: { - enableHighAccuracy: true, - }, icon: 'fa-map-marker', clickBehavior: { inView: 'stop', outOfView: 'setView', inViewNotFollowing: 'setView' }, + circlePadding: [5, 5], }; constructor(private mapService: MapService, private geoJsonService: GeojsonService) { @@ -77,7 +75,7 @@ export class MapComponent implements OnChanges { * @param idVoie Street reference */ public getCoord(idVoie: number): Observable<GeoJson> { - return this.geoJsonService.getAddress(idVoie).pipe(mergeMap((res) => this.geoJsonService.getCoord(res))); + return this.geoJsonService.getAddressByIdVoie(idVoie).pipe(mergeMap((res) => this.geoJsonService.getCoord(res))); } /** @@ -113,10 +111,25 @@ export class MapComponent implements OnChanges { width: 256, height: 256, }; - const carteLayer = tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { - maxZoom: 20, + // Init WMS service with param from data.grandlyon.com + const carteLayer = new TileLayer.WMS('https://openstreetmap.data.grandlyon.com/wms', { + crs: CRS.EPSG3857, + transparent: true, + format: 'image/png', attribution: 'Map data © OpenStreetMap contributors', + version: '1.3.0', + maxZoom: 20, }); + carteLayer.wmsParams = { + format: 'image/png', + transparent: true, + version: '1.3.0', + layers: 'osm_grandlyon', + service: 'WMS', + request: 'GetMap', + width: 256, + height: 256, + }; // Center is set on townhall // Zoom is blocked on 11 to prevent people to zoom out from metropole this.mapOptions = { diff --git a/src/app/map/services/geojson.service.ts b/src/app/map/services/geojson.service.ts deleted file mode 100644 index 7af9e34814df849adb8a6829c1ab4dc3979154f8..0000000000000000000000000000000000000000 --- a/src/app/map/services/geojson.service.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { Address } from '../models/address.model'; -import { GeoJson } from '../models/geojson.model'; - -@Injectable({ - providedIn: 'root', -}) -export class GeojsonService { - constructor(private http: HttpClient) {} - - /** - * Retrive an address with a street national reference - * @param idVoie Number - */ - public getAddress(idVoie: number): Observable<Address> { - return this.http - .get('/base-adresse/base-adresse-nationale/streets' + '?id=' + idVoie) - .pipe(map((data: { data: any[]; err: number }) => new Address(data.data[0]))); - } - - /** - * Get GeoLocation with an address - * @param address Address - */ - public getCoord(address: Address): Observable<GeoJson> { - return this.http - .get('/geocoding/photon/api' + '?q=' + address.queryString()) - .pipe(map((data: { features: any[]; type: string }) => new GeoJson(data.features[0]))); - } -} diff --git a/src/app/map/models/address.model.ts b/src/app/models/address.model.ts similarity index 100% rename from src/app/map/models/address.model.ts rename to src/app/models/address.model.ts diff --git a/src/app/models/structure.model.ts b/src/app/models/structure.model.ts index 191917321f193d1b9442d5e66fa2a8eeecac84d6..278f117d7a9e910931c86442814314c6d1da4459 100644 --- a/src/app/models/structure.model.ts +++ b/src/app/models/structure.model.ts @@ -31,6 +31,7 @@ export class Structure { public hours: Week; public isOpen: boolean; public openedOn: OpeningDay; + public distance?: string; constructor(obj?: any) { Object.assign(this, obj, { diff --git a/src/app/map/services/geojson.service.spec.ts b/src/app/services/geojson.service.spec.ts similarity index 93% rename from src/app/map/services/geojson.service.spec.ts rename to src/app/services/geojson.service.spec.ts index 359674551c7ffd2fab202edd37c6013365bfedb9..f6b6c9bda4c9fe63ebbd5a76bf63bc2486b3268a 100644 --- a/src/app/map/services/geojson.service.spec.ts +++ b/src/app/services/geojson.service.spec.ts @@ -24,14 +24,13 @@ describe('GeojsonService', () => { it('should get address for id 26061 ', async () => { await new Promise((resolve) => { - service.getAddress(26061).subscribe( + service.getAddressByIdVoie(26061).subscribe( (val) => { expect(val.zipcode).toEqual('69800'); expect(val.text).toEqual('13ème Rue Cité Berliet'); resolve(); }, (err) => { - console.log(err); resolve(); } ); @@ -47,7 +46,6 @@ describe('GeojsonService', () => { resolve(); }, (err) => { - console.log(err); resolve(); } ); diff --git a/src/app/services/geojson.service.ts b/src/app/services/geojson.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..39aed758f5cf06e5834199bb4bde3df374559153 --- /dev/null +++ b/src/app/services/geojson.service.ts @@ -0,0 +1,99 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Address } from '../models/address.model'; +import { GeoJson } from '../map/models/geojson.model'; + +@Injectable({ + providedIn: 'root', +}) +export class GeojsonService { + constructor(private http: HttpClient) {} + + /** + * Retrive an address with a street national reference + * @param idVoie Number + */ + public getAddressByIdVoie(idVoie: number): Observable<Address> { + return this.http + .get('/base-adresse/base-adresse-nationale/streets' + '?id=' + idVoie) + .pipe(map((data: { data: any[]; err: number }) => new Address(data.data[0]))); + } + + /** + * Retrive an address by geolocation + * @param idVoie Number + */ + public getAddressByCoord(longitude: number, latitude: number): Observable<any> { + return this.http + .get('/reverse/' + '?lon=' + longitude + '&lat=' + latitude) + .pipe(map((data: { features: any[] }) => new GeoJson(data.features[0]))); + } + + /** + * Get GeoLocation with an address + * @param address Address + */ + public getCoord(address: Address): Observable<GeoJson> { + return this.http + .get('/geocoding/photon/api' + '?q=' + address.queryString()) + .pipe(map((data: { features: any[]; type: string }) => new GeoJson(data.features[0]))); + } + + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + // ::: ::: + // ::: This routine calculates the distance between two points (given the ::: + // ::: latitude/longitude of those points). It is being used to calculate ::: + // ::: the distance between two locations using GeoDataSource (TM) prodducts ::: + // ::: ::: + // ::: Definitions: ::: + // ::: South latitudes are negative, east longitudes are positive ::: + // ::: ::: + // ::: Passed to function: ::: + // ::: lat1, lon1 = Latitude and Longitude of point 1 (in decimal degrees) ::: + // ::: lat2, lon2 = Latitude and Longitude of point 2 (in decimal degrees) ::: + // ::: unit = the unit you desire for results ::: + // ::: where: 'M' is statute miles (default) ::: + // ::: 'K' is kilometers ::: + // ::: 'N' is nautical miles ::: + // ::: ::: + // ::: Worldwide cities and other features databases with latitude longitude ::: + // ::: are available at https://www.geodatasource.com ::: + // ::: ::: + // ::: For enquiries, please contact sales@geodatasource.com ::: + // ::: ::: + // ::: Official Web site: https://www.geodatasource.com ::: + // ::: ::: + // ::: GeoDataSource.com (C) All Rights Reserved 2018 ::: + // ::: ::: + // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + + public getDistance(lat1, lon1, lat2, lon2, unit): string { + if (lat1 === lat2 && lon1 === lon2) { + return '0'; + } else { + const radlat1 = (Math.PI * lat1) / 180; + const radlat2 = (Math.PI * lat2) / 180; + const theta = lon1 - lon2; + const radtheta = (Math.PI * theta) / 180; + let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); + if (dist > 1) { + dist = 1; + } + dist = Math.acos(dist); + dist = (dist * 180) / Math.PI; + dist = dist * 60 * 1.1515; + if (unit === 'K') { + dist = dist * 1.609344; + } + if (unit === 'M') { + dist = dist * 1.609344 * 1000; + } + if (unit === 'N') { + dist = dist * 0.8684; + } + return dist.toFixed(0); + } + } +} diff --git a/src/app/structure-list/components/card/card.component.html b/src/app/structure-list/components/card/card.component.html index b8d0c1dc5ddb3535aaf7aac8648c35965c0b45b2..daf64b16c5231456297a97f3b413e927c200cd96 100644 --- a/src/app/structure-list/components/card/card.component.html +++ b/src/app/structure-list/components/card/card.component.html @@ -3,7 +3,7 @@ <div class="headerStructure" fxLayout="row" fxLayoutAlign="space-between center"> <span class="typeStructure">{{ structure.typeDeStructure }}</span> - <span class="distanceStructure">├─┤ 63 m</span> + <span *ngIf="structure.distance" class="distanceStructure">├─┤ {{ this.formatDistance() }}</span> </div> <br /> <div class="statusStructure" fxLayout="row" fxLayoutAlign="start center"> diff --git a/src/app/structure-list/components/card/card.component.ts b/src/app/structure-list/components/card/card.component.ts index 9049bcfa750403198f6e1383b1ef24d980a6f5b5..0b93efe9ebd5604aa1542db6a3b62f7e50e4d080 100644 --- a/src/app/structure-list/components/card/card.component.ts +++ b/src/app/structure-list/components/card/card.component.ts @@ -1,5 +1,9 @@ import { Component, Input, OnInit } from '@angular/core'; import { Structure } from '../../../models/structure.model'; +import { GeojsonService } from '../../../services/geojson.service'; +import { GeoJson } from '../../../map/models/geojson.model'; +import { Observable } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; @Component({ selector: 'app-card', @@ -8,7 +12,26 @@ import { Structure } from '../../../models/structure.model'; }) export class CardComponent implements OnInit { @Input() public structure: Structure; - constructor() {} + constructor(private geoJsonService: GeojsonService) {} ngOnInit(): void {} + + /** + * Display distance in m or km according to value + */ + public formatDistance(): string { + if (this.structure.distance.length > 3) { + return (parseInt(this.structure.distance, 10) / 1000).toFixed(1).toString() + ' km'; + } else { + return this.structure.distance + ' m'; + } + } + + /** + * Get coord with a street reference + * @param idVoie Street reference + */ + public getCoord(idVoie: number): Observable<GeoJson> { + return this.geoJsonService.getAddressByIdVoie(idVoie).pipe(mergeMap((res) => this.geoJsonService.getCoord(res))); + } } diff --git a/src/app/structure-list/components/recherche/recherche.component.ts b/src/app/structure-list/components/recherche/recherche.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ccca06c4ef0ec4a4a0343d76bec7e900352b6c5 --- /dev/null +++ b/src/app/structure-list/components/recherche/recherche.component.ts @@ -0,0 +1,12 @@ +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'app-recherche', + templateUrl: './recherche.component.html', + styleUrls: ['./recherche.component.scss'], +}) +export class RechercheComponent implements OnInit { + constructor() {} + + ngOnInit(): void {} +} diff --git a/src/app/structure-list/structure-list.component.spec.ts b/src/app/structure-list/structure-list.component.spec.ts index b00f05ee5b5fe64013449fe33fb9145ad13f448f..ea045c5516a8aa5f1f7d67582eb98a9298f73756 100644 --- a/src/app/structure-list/structure-list.component.spec.ts +++ b/src/app/structure-list/structure-list.component.spec.ts @@ -163,7 +163,6 @@ describe('StructureListComponent', () => { }) ); structureList.length = 4; - console.log(structureList.length); component.structureList = structureList; fixture.detectChanges(); // calls NgOnit }); diff --git a/src/app/structure-list/structure-list.component.ts b/src/app/structure-list/structure-list.component.ts index 26eda111435ce336a15e7b6f63c1305251cdf831..5152fa5fb3e98be820085ff6db2f85b43b6f66b6 100644 --- a/src/app/structure-list/structure-list.component.ts +++ b/src/app/structure-list/structure-list.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Filter } from './models/filter.model'; import { Structure } from '../models/structure.model'; - +import { GeoJson } from '../map/models/geojson.model'; @Component({ selector: 'app-structure-list', templateUrl: './structure-list.component.html', @@ -10,11 +10,12 @@ import { Structure } from '../models/structure.model'; export class StructureListComponent implements OnInit { @Input() public structureList: Structure[]; @Output() searchEvent = new EventEmitter(); + @Input() public location: GeoJson; constructor() {} ngOnInit(): void {} - fetchResults(filters: Filter[]) { + public fetchResults(filters: Filter[]): void { this.searchEvent.emit(filters); } } diff --git a/src/assets/logos/ram_logo.svg b/src/assets/logos/ram_logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..d8bd6173f4eab84d1adc18939d150f298eea15df --- /dev/null +++ b/src/assets/logos/ram_logo.svg @@ -0,0 +1,9 @@ +<svg width="127" height="70" viewBox="0 0 127 70" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M24.1764 46.3997V30.7881M24.1764 30.7881V17.6632C24.1764 17.1109 24.6234 16.6632 25.1757 16.6632C27.6739 16.6632 31.7707 16.6632 33.8408 16.6632C36.8145 16.6632 40.5315 18.15 40.5315 24.0973C40.5315 30.0446 34.5842 30.7881 27.8935 30.7881C22.5409 30.7881 23.1852 30.7881 24.1764 30.7881Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M42.0178 46.3998C42.2656 46.8954 42.0225 46.3998 40.531 42.6828C38.4226 37.4283 36.0726 30.7881 26.4082 30.7881H22" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M49.4521 46.4028C50.4434 42.1902 52.8718 32.5754 54.656 27.8175C56.4402 23.0597 58.8687 18.401 59.8599 16.6663C60.8511 17.9053 63.4283 21.8702 65.8072 27.8175C68.1862 33.7648 70.7633 42.6858 71.7545 46.4028" stroke="#D50000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M49.5063 31.5336L66.8166 31.5336" stroke="#D50000" stroke-width="2" stroke-linecap="round"/> +<path d="M91.0928 33.7628C91.6875 34.9522 92.3318 34.2584 92.5796 33.7628C95.5533 28.8067 101.649 18.5972 102.244 17.4077C102.987 15.9209 104.474 15.1775 104.474 17.4077C104.474 19.1919 104.474 37.7276 104.474 46.4008" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M92.5771 33.7628C91.9824 34.9522 91.3381 34.2584 91.0903 33.7628C88.1167 28.8067 82.0207 18.5972 81.426 17.4077C80.6825 15.9209 79.1957 15.1775 79.1957 17.4077C79.1957 19.1919 79.1957 37.7276 79.1957 46.4008" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M41.7603 45.7377L43.9372 50.7621C44.571 52.225 46.0131 53.1718 47.6075 53.1718L75.243 53.1718C77.4521 53.1718 79.243 51.3809 79.243 49.1718V45.7377" stroke="black" stroke-width="2"/> +</svg> diff --git a/src/assets/scss/_color.scss b/src/assets/scss/_color.scss index 319c1008e5eabeb80fb3863499523b450c8861d9..40d9d77e3b89be9186bee9f0382ec6301ca8d564 100644 --- a/src/assets/scss/_color.scss +++ b/src/assets/scss/_color.scss @@ -4,7 +4,7 @@ $white: #ffff; /* GREYS */ $grey: #b4bbbf; $grey-1: #333333; -$grey-2: #eeeeee; +$grey-2: #4f4f4f; $grey-3: #c3c3c3; $grey-4: #f2ecf2; $grey-5: #f2f2f2; @@ -19,8 +19,11 @@ $orange-3: #fff4ea; $green: #41c29c; /* OTHERS */ $blue: #b3f8f8; +$blue-dark: #2f5480; $purple: #594d59; +$red-metro: #d50000; /* APP COLORS */ $primary-color: $yellow; $secondary-color: $orange; -$default-link-color: $grey-1; +$default-link-color: $grey-2; +$button-secondary: $blue-dark; diff --git a/src/assets/scss/_icons.scss b/src/assets/scss/_icons.scss index 66a25e9c37760833140b22246f49d17c9ead7a42..77ed9a879173fa07af1b9d77d57ba30b5e2cda47 100644 --- a/src/assets/scss/_icons.scss +++ b/src/assets/scss/_icons.scss @@ -5,7 +5,7 @@ display: inline-block; background: transparent; border-radius: 30px; - border: 2px solid $grey; + border: 2px solid $purple; height: 12px; width: 12px; min-width: 12px; @@ -15,7 +15,7 @@ content: ''; height: 2px; width: 8px; - background: $grey; + background: $purple; position: absolute; top: 14px; left: 10px; @@ -24,20 +24,6 @@ -ms-transform: rotate(45deg); -o-transform: rotate(45deg); } - - &.orange { - border: 2px solid $orange; - &:after { - background: $orange; - } - } - - &.purple { - border: 2px solid $purple; - &:after { - background: $purple; - } - } } .ico-pin { @@ -120,3 +106,20 @@ position: absolute; border-radius: 50%; } + +.ico-profile { + .head { + width: 12px; + height: 12px; + border-radius: 25px; + border: 2px solid $purple; + } + .body { + width: 28px; + height: 10px; + border-radius: 20px 20px 0px 0px; + border-top: 2px solid $purple; + border-right: 2px solid $purple; + border-left: 2px solid $purple; + } +} diff --git a/src/assets/scss/_layout.scss b/src/assets/scss/_layout.scss index a6d8e0635de2572ac516fa73205b57c2ad7206df..6e84660e0325b17dafd7f25ecd84c7a1861d6493 100644 --- a/src/assets/scss/_layout.scss +++ b/src/assets/scss/_layout.scss @@ -1,2 +1,2 @@ -$header-height: 80px; +$header-height: 70px; $footer-height: 56px; diff --git a/src/assets/scss/_shapes.scss b/src/assets/scss/_shapes.scss index 22281d819191fb527d9bfd1eb5169624320278bf..164a637b1ee84315559a0c847215022b8324ce97 100644 --- a/src/assets/scss/_shapes.scss +++ b/src/assets/scss/_shapes.scss @@ -48,7 +48,7 @@ $mat-tab-shadow: 0px 2px 7px rgba(0, 0, 0, 0.25); ); background-size: 5px 5px; background-position: 25px 25px; - padding: 0 0 8px 8px; + padding: 0 0 6px 6px; .body-wrap { background-color: $white; } diff --git a/src/assets/scss/_typography.scss b/src/assets/scss/_typography.scss index 0fd92b3fcd00781c6a73390b0286a8c9d4db89f6..5ddcc8aa3f484ba8cea776b499c0ec0005779d9c 100644 --- a/src/assets/scss/_typography.scss +++ b/src/assets/scss/_typography.scss @@ -1,4 +1,4 @@ -$text-font: 'Trebuchet MS'; +$text-font: 'Trebuchet MS', 'Helvetica', sans-serif; $footer-text-font: 'Arial', 'Helvetica', sans-serif; $title-font: 'Courier New', 'Helvetica', sans-serif; diff --git a/src/styles.scss b/src/styles.scss index 0a1dafb138e69161d599a9e59e67c1a9c0c8c734..b0d48863fdf943b1691be9881d97b74f68beef03 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -12,12 +12,14 @@ body { height: 100%; margin: 0; padding: 0; + background-color: $grey-5; } a { color: $default-link-color; text-decoration: none; background-color: transparent; + @include cn-regular-14; cursor: pointer; &:focus { text-decoration: none; @@ -34,7 +36,7 @@ a { // Containers .content-container { margin: 0; - padding: 60px 0 30px 0; + padding: 20px 0 30px 0; overflow-y: auto; overflow-x: hidden; width: 100%;