diff --git a/src/app/geosource/models/dataset.model.ts b/src/app/geosource/models/dataset.model.ts index 9bdd8aa159375af8cb2ca18942b39ac791bc2c9d..ffa65b99f74756ae90759dbfbec490c030ce7599 100644 --- a/src/app/geosource/models/dataset.model.ts +++ b/src/app/geosource/models/dataset.model.ts @@ -28,6 +28,7 @@ export class Dataset { this.metadata = new Metadata(data.metadata); this.data = data.data ? data.data : []; this.totalData = data.totalData ? data.totalData : 0; + this.metadata.total_documents = this.totalData; this.editorialMetadata = new EditorialMetadata(data.editorialMetadata); } } diff --git a/src/app/geosource/services/dataset-detail.service.ts b/src/app/geosource/services/dataset-detail.service.ts index 83f66f996f52d00f401f5c73ae377b142131c9c2..9485bbdccc0ad2198790c8a695a2d0de3dcb7fde 100644 --- a/src/app/geosource/services/dataset-detail.service.ts +++ b/src/app/geosource/services/dataset-detail.service.ts @@ -55,6 +55,7 @@ export class DatasetDetailService { map((e) => { if (e.hits.hits.length > 0) { const metadata = new Metadata(e.hits.hits[0]._source['metadata-fr']); + metadata.total_documents = e.hits.total; metadata.dataset_id = e.hits.hits[0]._id; const bbox = e.hits.hits[0]._source['metadata-fr']['bbox']; if (bbox !== undefined && bbox['coordinates'] !== undefined) { diff --git a/src/app/map/components/map.component.html b/src/app/map/components/map.component.html index 962a141969fe0d42adf5f173f432f5aea19e8468..0a67388a46113e72be072275642a59d07e6315b5 100644 --- a/src/app/map/components/map.component.html +++ b/src/app/map/components/map.component.html @@ -43,6 +43,7 @@ <input type="text" class="input" id="mapUrlCopy" [value]="mapUrl()" #mapUrlElement> <button type="button" (click)="copyMaplink(mapUrlElement)" class="button is-medium"> + {{zoom()}} <svg xmlns="http://www.w3.org/2000/svg" id="copy" viewBox="0 0 18 18"> <path d="M11.1 7.1c.6.6 1 1.3 1.2 2.2s.2 1.6 0 2.4-.6 1.5-1.2 2.1L8.9 16c-.6.6-1.3 1-2.1 1.2-.8.2-1.6.2-2.4 0-.8-.2-1.5-.6-2.2-1.3-.6-.6-1-1.3-1.2-2.1-.2-.8-.2-1.6 0-2.4.2-.8.6-1.5 1.2-2.1l1.2-1.2c.2-.2.3-.2.5-.1s.3.2.3.4c0 .6.1 1.1.3 1.7.1.2 0 .4-.1.5L4 11c-.4.4-.7 1-.7 1.6 0 .6.2 1.1.7 1.6.4.4 1 .7 1.6.7s1.2-.2 1.6-.7l2.1-2.1c.4-.4.7-1 .7-1.6s-.3-1.1-.7-1.6c-.1-.1-.2-.2-.4-.3s-.1-.2-.1-.3c0-.4.1-.7.3-.9l.7-.7c.1-.1.2-.1.3-.1.1 0 .2 0 .3.1.2.1.5.2.7.4zm4.4-4.4c-.6-.6-1.3-1-2.2-1.2-.8-.2-1.6-.2-2.4 0-.8.2-1.5.6-2.1 1.2L6.6 4.9c-.6.6-1 1.3-1.2 2.1s-.2 1.6 0 2.4.6 1.5 1.2 2.2c.2.2.4.4.7.5.1.1.2.1.3.1.1 0 .2-.1.3-.2l.7-.7c.2-.2.4-.6.3-.9 0-.2-.1-.3-.2-.4s-.2-.1-.3-.2c-.4-.4-.6-1-.6-1.6S8 7 8.4 6.6l2.1-2.1c.4-.4 1-.7 1.6-.7s1.2.2 1.6.7c.4.4.6 1 .6 1.6 0 .6-.2 1.1-.7 1.6l-.4.4c-.1.1-.2.3-.1.5.2.5.3 1.1.3 1.7 0 .2.1.4.3.4.2.1.4.1.5-.1l1.2-1.2c.6-.6 1-1.3 1.2-2.1.2-.8.2-1.6 0-2.4-.1-.8-.5-1.5-1.1-2.2z" class="grey"/> <path d="M11.1 7.1c.6.6 1 1.3 1.2 2.2s.2 1.6 0 2.4-.6 1.5-1.2 2.1L8.9 16c-.6.6-1.3 1-2.1 1.2-.8.2-1.6.2-2.4 0-.8-.2-1.5-.6-2.2-1.3-.6-.6-1-1.3-1.2-2.1-.2-.8-.2-1.6 0-2.4.2-.8.6-1.5 1.2-2.1l1.2-1.2c.2-.2.3-.2.5-.1s.3.2.3.4c0 .6.1 1.1.3 1.7.1.2 0 .4-.1.5L4 11c-.4.4-.7 1-.7 1.6 0 .6.2 1.1.7 1.6.4.4 1 .7 1.6.7s1.2-.2 1.6-.7l2.1-2.1c.4-.4.7-1 .7-1.6s-.3-1.1-.7-1.6c-.1-.1-.2-.2-.4-.3s-.1-.2-.1-.3c0-.4.1-.7.3-.9l.7-.7c.1-.1.2-.1.3-.1.1 0 .2 0 .3.1.2.1.5.2.7.4zm4.4-4.4c-.6-.6-1.3-1-2.2-1.2-.8-.2-1.6-.2-2.4 0-.8.2-1.5.6-2.1 1.2L6.6 4.9c-.6.6-1 1.3-1.2 2.1s-.2 1.6 0 2.4.6 1.5 1.2 2.2c.2.2.4.4.7.5.1.1.2.1.3.1.1 0 .2-.1.3-.2l.7-.7c.2-.2.4-.6.3-.9 0-.2-.1-.3-.2-.4s-.2-.1-.3-.2c-.4-.4-.6-1-.6-1.6S8 7 8.4 6.6l2.1-2.1c.4-.4 1-.7 1.6-.7s1.2.2 1.6.7c.4.4.6 1 .6 1.6 0 .6-.2 1.1-.7 1.6l-.4.4c-.1.1-.2.3-.1.5.2.5.3 1.1.3 1.7 0 .2.1.4.3.4.2.1.4.1.5-.1l1.2-1.2c.6-.6 1-1.3 1.2-2.1.2-.8.2-1.6 0-2.4-.1-.8-.5-1.5-1.1-2.2z" class="grey"/> @@ -51,32 +52,6 @@ </button> </div> - <div class="columns is-mobile update-map" *ngIf="displayUpdateFeatures"> - <div class="column is-narrow"> - <div class="column-content"> - <a class="button is-medium" (click)="updateFeatures()"> - <span i18n="@@dataset.detail.map.search">Search this area</span> - <span class="icon" *ngIf="displaySpinner"> - <i class="fas fa-spinner fa-spin"></i> - </span> - </a> - </div> - </div> - </div> - <div class="columns is-mobile update-map" *ngIf="displayMessageLimitedFeatures && currentLayerType === linkFormats.wfs"> - <div class="column is-narrow"> - <div class="column-content"> - <div class="message is-medium"> - <p class=""><span>{{settings.maxDisplayFeatures}} </span> - <span i18n="@@dataset.detail.map.message1">records out of</span> - <span class="has-text-danger"> {{totalData}} </span> - <span i18n="@@dataset.detail.map.message2">in the displayed area</span></p> - <p class="" i18n="@@dataset.detail.map.message3">More details on zoom</p> - </div> - </div> - </div> - </div> - <div class="data-details-wrapper" *ngIf="selectedData !== null" [ngClass]="{'data-details-opened': selectedData !== null && displayDataDetails === true, 'data-details-closed': displayDataDetails === false}"> <app-data-details (close)="closeDataDetails()" [properties]="selectedData" class="app-data-details"></app-data-details> </div> diff --git a/src/app/map/components/map.component.scss b/src/app/map/components/map.component.scss index 122d5c2ef8534253d521d7ef8c1fe6464d47afe4..f977f5940c978156b482523890a7d798cf1c0865 100644 --- a/src/app/map/components/map.component.scss +++ b/src/app/map/components/map.component.scss @@ -105,41 +105,6 @@ } } -.update-map { - z-index: 1; - position: absolute; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - margin-top: 0; - width: 20%; - - .button { - box-shadow: 0 2px 5px -1px rgba(0, 0, 0, 0.3); - border: none; - text-decoration: none; - - &:hover { - background-color: #f7f7f7; - } - } - - .message { - padding: 0.5em; - cursor: default; - box-shadow: 0 2px 5px -1px rgba(0, 0, 0, 0.3); - border: none; - background-color: #fff; - text-align: center; - } - - .span.message3::before { - content: '\A'; - white-space: pre; - } -} - #menu { z-index: 1; position: absolute; diff --git a/src/app/map/components/map.component.ts b/src/app/map/components/map.component.ts index 3fa1a34c502395dae1b0da32392a2d09ee3baa75..e5d3d5cb3b9590ba6778d54c1c1f3b34fb3107c6 100644 --- a/src/app/map/components/map.component.ts +++ b/src/app/map/components/map.component.ts @@ -22,7 +22,6 @@ export class MapComponent implements OnInit, OnDestroy { metadata: Metadata; sub: Subscription; // Subscription to dataset change - featuresToChange: Subscription; map: mapboxgl.Map; @@ -34,12 +33,6 @@ export class MapComponent implements OnInit, OnDestroy { displayDataDetails = false; // Wether the data details should be displayed or not display3d = false; - // Attributes to manage the display of the pudate map button - displayUpdateFeatures = false; - displaySpinner = false; - displayMessageLimitedFeatures = false; - totalData = 0; - previousDatasetId: string; currentLayerType: string; @@ -73,25 +66,6 @@ export class MapComponent implements OnInit, OnDestroy { this.sub = this._datasetDetailService.dataset$.subscribe(() => { this.constructMap(); }); - - // Display the update button when the service tell us the need - // to update the features (after and zoom interaction) - this._mapService.featuresToUpdate$.subscribe(() => { - if (this._datasetDetailService.datasetDataNumber > this.settings.maxDisplayFeatures) { - this.displayUpdateFeatures = true; - } - this.displayMessageLimitedFeatures = false; - }); - - // When features have been loaded, we hide the update button - this._mapService.mapUpdated$.subscribe((totalData) => { - this.displayUpdateFeatures = false; - this.displaySpinner = false; - if (totalData > this.settings.maxDisplayFeatures && this._datasetDetailService.datasetDataNumber < 100000) { - this.displayMessageLimitedFeatures = true; - this.totalData = totalData; - } - }); } // To avoid call the constructMap when we left the component @@ -163,10 +137,6 @@ export class MapComponent implements OnInit, OnDestroy { this.map.on('style.load', () => { this._mapService.addLayers(); this.currentLayerType = this._mapService.currentLayerType; - // When we come from a copy link, we need to update the features with the new bounding box - if (parameters.length === 6) { - this.updateFeatures(); - } }); this._mapService.previousDatasetId = this.metadata.dataset_id; @@ -178,9 +148,8 @@ export class MapComponent implements OnInit, OnDestroy { this._mapService.switchLayer(baseLayer); } - updateFeatures() { - this.displaySpinner = true; - this._mapService.updateFeatures(); + zoom() { + return this.map.getZoom(); } // Looks for the language to be used, if not indicated in the url takes the navigator default language diff --git a/src/app/map/services/map.service.ts b/src/app/map/services/map.service.ts index dacea3736f58a0e90ab0a296d2f5ecaf540e490e..c23e75300b3f67a8fa52475b7c4919fd07fcff5c 100644 --- a/src/app/map/services/map.service.ts +++ b/src/app/map/services/map.service.ts @@ -55,8 +55,8 @@ export class MapService { // Properties used to send information to the component // about the features state (need to update or not) - private _mapToUpdate = new Subject<any>(); - private _mapUpdated = new Subject<any>(); + // private _mapToUpdate = new Subject<any>(); + // private _mapUpdated = new Subject<any>(); _errorService: any; @@ -68,6 +68,7 @@ export class MapService { createMap(url: string, baseLayer: any, addControls: boolean, options?: mapboxgl.MapboxOptions): mapboxgl.Map { this.metadata = this._datasetDetailService.datasetMetadata; + console.log(this.metadata.total_documents); this.uriWFS = this.metadata.link.find((e) => { return e.service === linkFormats.wfs; }); // Reset to false in ordre to set event listener @@ -118,7 +119,6 @@ export class MapService { this._map.addControl(this.minimap, 'bottom-left'); } - this._mapUpdated.next(this.totalData); }); } this._mapSubject.next(); @@ -134,362 +134,252 @@ export class MapService { // - if WFS exists but data > 100 000, or if WFS no exist: we display the WMS layer addLayers() { // Check if the metadata has WFS data format. - if (this.uriWFS && this._datasetDetailService.datasetDataNumber < 100000) { - this._currentLayerType = linkFormats.wfs; - this.getWFSFeatures( - this.metadata, - settings.maxDisplayFeatures).subscribe((geojson) => { - this.geojson = geojson; - this.addWFSLayer(); - }); - this._map - .on('zoomend', () => { - this._mapToUpdate.next(); - }) - .on('moveend', () => { - this._mapToUpdate.next(); - }); - - } else { - this._currentLayerType = linkFormats.wms; - const uriWMS = this.metadata.link.find((e) => { return e.service === linkFormats.wms; }); - this.addWMSLayer(uriWMS); - } - } - - // Get the features inside the bounds of the map, and update the data - // associated to the source of the map - updateFeatures() { - this.closePanel(); - const bounds = this._map.getBounds(); - this.getWFSFeatures(this.metadata, settings.maxDisplayFeatures, bounds).subscribe((geojson) => { - this.geojson = geojson; - const source1 = this._map.getSource('wfs-clustered-points') as mapboxgl.GeoJSONSource; - const source2 = this._map.getSource('wfs-polygon') as mapboxgl.GeoJSONSource; - source1.setData(geojson); - source2.setData(geojson); - // Notify to the component that the map has been ipdated with new features - this._mapUpdated.next(this.totalData); - }); + this._currentLayerType = linkFormats.wfs; + this.addMVTLayers(); + // this.getMVTFeatures( + // this.metadata, + // settings.maxDisplayFeatures).subscribe((geojson) => { + // this.geojson = geojson; + // this.addMVTLayers(); + // }); + // this._map + // .on('zoomend', () => { + // this._mapToUpdate.next(); + // }) + // .on('moveend', () => { + // this._mapToUpdate.next(); + // }); + // if (this.uriWFS && this._datasetDetailService.datasetDataNumber < 100000) { + // this._currentLayerType = linkFormats.wfs; + // this.getMVTFeatures( + // this.metadata, + // settings.maxDisplayFeatures).subscribe((geojson) => { + // this.geojson = geojson; + // this.addMVTLayers(); + // }); + // this._map + // .on('zoomend', () => { + // this._mapToUpdate.next(); + // }) + // .on('moveend', () => { + // this._mapToUpdate.next(); + // }); + + // } else { + // this._currentLayerType = linkFormats.wms; + // const uriWMS = this.metadata.link.find((e) => { return e.service === linkFormats.wms; }); + // this.addWMSLayer(uriWMS); + // } } // - Fetch data from ES // - Add it to the map source // - Create the WFS layers from this source // - if the features are 'Point' type, create clustering layers - addWFSLayer() { - console.log(this.metadata); + addMVTLayers() { const url = 'https://download.recette.data.grandlyon.com/mvt/grandlyon?LAYERS' + `=${this.uriWFS.name}&map.imagetype=mvt&tilemode=gmap&tile={x}+{y}+{z}&mode=tile`; - console.log(url); - // this._map.addSource('wfs-clustered-points', { - // type: 'geojson', - // tiles: url, - // cluster: true, - // clusterMaxZoom: 13, // Max zoom to cluster points on - // clusterRadius: 45, // Radius of each cluster when clustering points (defaults to 50) - // }); - - this._map.addSource('wfs-clustered-points', { - type: 'vector', - tiles: [url], - }); - // this._map.addSource('wfs-polygon', { - // type: 'geojson', - // data: this.geojson, - // }); - - this._map.addSource('wfs-polygon', { + this._map.addSource('vector-source', { type: 'vector', tiles: [url], }); - // Add the layers for 'Point' features (clustered and unclustered layers) - // Create steps to display different circle size and colors depending the count - if (false) { - this._map.addLayer({ - id: 'point-features', - type: 'circle', - source: 'wfs-clustered-points', - paint: { - // Use step expressions (https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-step) - // with three steps to implement three types of circles: - // * 20px circles when point count is less than 20 - // * 30px circles when point count is between 20 and 50 - // * 40px circles when point count is greater than or equal to 50 - 'circle-color': this.featureColor, - 'circle-radius': [ - 'step', - ['get', 'point_count'], - 20, // 20px - 20, // les than 20 features - 30, // 30px - 50, // until - more 50 features - 40, // 40px - ], - 'circle-stroke-width': [ - 'step', - ['get', 'point_count'], - 4, // 4px - 20, // les than 20 features - 6, // 6px - 50, // until - more 50 features - 11, // 11px - ], - 'circle-stroke-color': this.featureHoverColor, - }, - filter: ['has', 'point_count'], - }); - } - - // Add the cluster count layer (the number inside the circle) - if (false) { - this._map.addLayer({ - id: 'cluster-count', - type: 'symbol', - source: 'wfs-clustered-points', - filter: ['has', 'point_count'], - layout: { - 'text-field': '{point_count_abbreviated}', - 'text-size': 14, - 'text-font': ['Noto Sans Bold'], - }, - paint: { - 'text-color': 'white', - }, - }); - } - - // For 'Polygon' feature highlighted - if (false) { // TODO Remove - this._map.addLayer({ - id: 'polygon-features-highlight', - type: 'fill', - layout: { - visibility: 'none', - }, - source: 'wfs-polygon', - paint: { - 'fill-color': this.featureHighlightedColor, - 'fill-opacity': 0.4, - }, - filter: ['==', '$type', 'Polygon'], - }); - } - // For 'Polygon' and 'MultiPolygon' features, one layer is enough (no cluster) + // For 'Polygon' and 'MultiPolygon' features this._map.addLayer( { id: 'polygon-features', type: 'fill', - source: 'wfs-polygon', + source: 'vector-source', 'source-layer': this.uriWFS.name, paint: { - 'fill-color': this.featureColor, + 'fill-color': ['case', + ['boolean', ['feature-state', 'hover'], false], + this.featureHoverColor, + ['boolean', ['feature-state', 'highlight'], false], + this.featureHighlightedColor, + this.featureColor, + ], 'fill-opacity': 0.4, }, filter: ['match', ['geometry-type'], ['Polygon', 'MultiPolygon'], true, false], }); - // For 'LineString' feature selected highlight - if (0) { // TODO Remove - this._map.addLayer({ - id: 'line-features-highlight', + // For 'LineString' and 'MultiLineString features + this._map.addLayer( + { + id: 'line-features', type: 'line', - source: 'wfs-polygon', + source: 'vector-source', + 'source-layer': this.uriWFS.name, layout: { 'line-cap': 'round', 'line-join': 'round', - visibility: 'none', }, paint: { - 'line-color': this.featureHighlightedColor, - 'line-width': 3, + 'line-color': ['case', + ['boolean', ['feature-state', 'hover'], false], + this.featureHoverColor, + ['boolean', ['feature-state', 'highlight'], false], + this.featureHighlightedColor, + this.featureColor, + ], + 'line-width':[ + 'interpolate', ['linear'], ['zoom'], + 12, 1, + 13, 2, + 16, 10, + ], 'line-opacity': 0.8, }, - }); - } - - // For 'LineString' and 'MultiLineString features, one layer is enough (no cluster) - if (false) { // TODO Remove - this._map.addLayer( - { - id: 'line-features', - type: 'line', - source: 'wfs-polygon', - 'source-layer': this.uriWFS.name, - layout: { - 'line-cap': 'round', - 'line-join': 'round', - }, - paint: { - 'line-color': this.featureColor, - 'line-width': 3, - 'line-opacity': 0.8, - }, - filter: ['match', ['geometry-type'], ['LineString', 'MultiLineString'], true, false], - }, - 'line-features-highlight', - ); - } - - // Add layer + style for the unclustered points highlighted - // this._map.addLayer( - // { - // id: 'unclustered-point-highlighted', - // type: 'circle', - // source: 'wfs-clustered-points', - // 'source-layer': this.uriWFS.name, - // filter: ['==', 'id', ''], - // layout: { - // visibility: 'none', - // }, - // paint: { - // 'circle-radius': { - // base: 1.75, - // stops: [[12, 2], [22, 180]], - // }, - // 'circle-color': this.featureHighlightedColor, - // }, - // }); - - // Add layer + style for the unclustered points + filter: ['match', ['geometry-type'], ['LineString', 'MultiLineString'], true, false], + }, + ); + // Add layer + style for the points + // Get paint options depending the dataset size + const paintOptions = this.getPointPaintOptions(this.metadata.total_documents); this._map.addLayer( { - id: 'unclustered-point', + id: 'point-features', type: 'circle', - source: 'wfs-clustered-points', + source: 'vector-source', 'source-layer': this.uriWFS.name, filter: ['match', ['geometry-type'], ['Point'], true, false], - paint: { - 'circle-radius': { - base: 1.75, - stops: [[12, 2], [22, 180]], - }, - 'circle-stroke-width': { - base: 0.3, - stops: [[12, 0.6], [22, 2]], - }, - 'circle-stroke-color': 'white', - 'circle-stroke-opacity': 0.7, - 'circle-color': ['case', - ['boolean', ['feature-state', 'hover'], false], - this.featureHoverColor, - ['boolean', ['feature-state', 'highlight'], false], - this.featureHighlightedColor, - this.featureColor, - ], - }, + paint: paintOptions, }); if (!this.eventPopupAdded) { - // Change the cursor to a pointer when the mouse is over the unclustered-point layer. - this._map.on('mousemove', 'unclustered-point', (e) => { - if (e.features.length > 0) { - this._map.getCanvas().style.cursor = 'pointer'; - if (this.hoveredFeatureId) { - this._map.setFeatureState({ - source: 'wfs-clustered-points', - sourceLayer: this.uriWFS.name, - id: this.hoveredFeatureId, - }, - // tslint:disable-next-line:align - { hover: false }); - } - - this.hoveredFeatureId = e.features[0].id; - - // Display hover state only if not already highlighted - const state = this._map.getFeatureState({ - source: 'wfs-clustered-points', - sourceLayer: this.uriWFS.name, - id: this.hoveredFeatureId, - }); - if (!state.highlight) { - this._map.setFeatureState({ - source: 'wfs-clustered-points', - sourceLayer: this.uriWFS.name, - id: this.hoveredFeatureId, - }, - // tslint:disable-next-line:align - { hover: true }); - } - } + // Manage the cursor and feature state for point-features layer when mouse events + this._map.on('mousemove', 'point-features', (e) => { + this.manageFeatureOnMouseMove(e.features); }); - - this._map.on('mouseleave', 'unclustered-point', (e) => { - if (this.hoveredFeatureId) { - this._map.getCanvas().style.cursor = ''; - this._map.setFeatureState({ - source: 'wfs-clustered-points', - sourceLayer: this.uriWFS.name, - id: this.hoveredFeatureId, - // tslint:disable-next-line:align - }, { hover: false }); - } - - this.hoveredFeatureId = null; + this._map.on('mouseleave', 'point-features', (e) => { + this.manageFeatureOnMouseEnd(); }); - this._map.on('mouseenter', 'polygon-features', () => { - this._map.getCanvas().style.cursor = 'pointer'; - }).on('mouseleave', 'polygon-features', () => { - this._map.getCanvas().style.cursor = ''; + // Manage the cursor and feature state for polygon-features layer when mouse events + this._map.on('mousemove', 'polygon-features', (e) => { + this.manageFeatureOnMouseMove(e.features); + }); + this._map.on('mouseleave', 'polygon-features', (e) => { + this.manageFeatureOnMouseEnd(); }); - this._map.on('mouseenter', 'line-features', () => { - this._map.getCanvas().style.cursor = 'pointer'; - }).on('mouseleave', 'line-features', () => { - this._map.getCanvas().style.cursor = ''; + // Manage the cursor and feature state for line-features layer when mouse events + this._map.on('mousemove', 'line-features', (e) => { + this.manageFeatureOnMouseMove(e.features); + }); + this._map.on('mouseleave', 'line-features', (e) => { + this.manageFeatureOnMouseEnd(); }); - // When a click event occurs on a feature in the states layer + // When a click event occurs on the map, close the information panel this._map.on('click', () => { - // Reset state of panel this.closePanel(); }); - this.addClickEventOnLayer('unclustered-point', this.uriWFS.name); - // this.addClickEventOnLayer('polygon-features', 'polygon-features-highlight'); - // if (this._map.getLayer('line-features-highlight')) { - // this.addClickEventOnLayer('line-features', 'line-features-highlight'); - // } + if (this._map.getLayer('point-features')) { + this.addClickEventOnLayer('point-features'); + } + if (this._map.getLayer('polygon-features')) { + this.addClickEventOnLayer('polygon-features'); + } + if (this._map.getLayer('line-features')) { + this.addClickEventOnLayer('line-features'); + } this.eventPopupAdded = true; } } - addClickEventOnLayer(layer, layerName) { + manageFeatureOnMouseMove(features: any) { + if (features.length > 0) { + this._map.getCanvas().style.cursor = 'pointer'; + if (this.hoveredFeatureId) { + this.changeFeatureState(this.hoveredFeatureId, { hover: false }); + } + + this.hoveredFeatureId = features[0].id; + + // Display hover state only if not already highlighted + const state = this._map.getFeatureState({ + source: 'vector-source', + sourceLayer: this.uriWFS.name, + id: this.hoveredFeatureId, + }); + if (!state.highlight) { + this.changeFeatureState(this.hoveredFeatureId, { hover: true }); + } + } + } + + manageFeatureOnMouseEnd() { + if (this.hoveredFeatureId) { + this._map.getCanvas().style.cursor = ''; + this.changeFeatureState(this.hoveredFeatureId, { hover: false }); + } + this.hoveredFeatureId = null; + } + + getPointPaintOptions(totalFeatures: number) { + const paintOptions = { + 'circle-stroke-color': 'white', + 'circle-stroke-width': [ + 'interpolate', ['linear'], ['zoom'], + 10, 0.3, + 12, 0.3, + 16, 0.7, + ], + 'circle-color': ['case', + ['boolean', ['feature-state', 'hover'], false], + this.featureHoverColor, + ['boolean', ['feature-state', 'highlight'], false], + this.featureHighlightedColor, + this.featureColor, + ], + }; + if (totalFeatures < 1000) { + paintOptions['circle-radius'] = [ + 'interpolate', ['linear'], ['zoom'], + 10, 1.75, + 12, 4, + 16, 10, + ]; + } else { + paintOptions['circle-radius'] = [ + 'interpolate', ['linear'], ['zoom'], + 12, 2.5, + 16, 8, + ]; + } + return paintOptions; + } + + // Change the state of one feature. + // State has the following format:{state: value} + // Ex: {hover: true, highlight: false} + changeFeatureState(featureId: string, state) { + this._map.setFeatureState( + { + source: 'vector-source', + sourceLayer: this.uriWFS.name, + id: featureId, + }, + state); + } + + addClickEventOnLayer(layer: string) { this._map.on('click', layer, (e) => { this.selectedFeature = e.features[0].id; // Reset the hover and highglithed state for the current and previous feature - this._map.setFeatureState({ - source: 'wfs-clustered-points', - sourceLayer: layerName, - id: this.hoveredFeatureId, - }, - // tslint:disable-next-line:align - { hover: false }); - this._map.setFeatureState({ - source: 'wfs-clustered-points', - sourceLayer: layerName, - id: this.highlightedFeatureId, - }, - // tslint:disable-next-line:align - { highlight: false }); + this.changeFeatureState(this.hoveredFeatureId, { highlight: false }); // Set highlited style for the current feature this.highlightedFeatureId = this.selectedFeature; - this._map.setFeatureState({ - source: 'wfs-clustered-points', - sourceLayer: layerName, - id: this.highlightedFeatureId, - }, - // tslint:disable-next-line:align - { highlight: true }); + this.changeFeatureState(this.highlightedFeatureId, { highlight: true }); const feature = e.features[0]; this.handleMapPosition(e.point.x, e.lngLat, () => { @@ -600,59 +490,6 @@ export class MapService { `/${this._map.getBearing()}/${this._map.getPitch()}/${this.selectedBaseLayer.id}`; } - // Get features from ES. Construct GeoJSON.FeatureCollection Object from it - // For 'MultiPoint' type feature, we explode it in multiple 'Point' type features - getWFSFeatures(metadata: Metadata, count: number, bounds?: mapboxgl.LngLatBounds): - Observable<GeoJSON.FeatureCollection> { - - let coordinates = null; - if (bounds) { - coordinates = []; - coordinates.push([bounds.getNorthWest().lng, bounds.getNorthWest().lat]); - coordinates.push([bounds.getSouthEast().lng, bounds.getSouthEast().lat]); - } - - return this._elasticSearchService.getDataByBbox( - metadata.geonet.uuid, - count, - coordinates).pipe( - map((elasticResponse) => { - const uriWFS = metadata.link.find((e) => { return e.service === linkFormats.wfs; }); - // Create the collection feature object - const featureCollection = { - type: 'FeatureCollection', - name: uriWFS.name, - features: [], - }; - - elasticResponse.hits.hits.forEach((e) => { - if (e._source['data-fr'] !== undefined) { - featureCollection.features.push(e._source['data-fr']); - } - }); - - this.totalData = elasticResponse.hits.total; - - const newFeatures = []; - // If the features are 'MultiPoint' type, explode it into multiple 'Point' - featureCollection.features.forEach((feature, index) => { - feature.properties['_featureId'] = index; - if (feature.geometry.type === 'MultiPoint') { - feature.geometry.coordinates.forEach((point) => { - const newFeature = Object.assign(feature); - newFeature.geometry.coordinates = point; - newFeature.geometry.type = 'Point'; - newFeatures.push(newFeature); - }); - } else { - newFeatures.push(feature); - } - }); - featureCollection.features = newFeatures; - return featureCollection as GeoJSON.FeatureCollection; - })); - } - // Used for WMS layer. // Get one feature from the coordinates getFeatureInfo(lng, lat) { @@ -712,29 +549,16 @@ export class MapService { closePanel() { // Remove the highlighted style for the feature - this._map.setFeatureState({ - source: 'wfs-clustered-points', - sourceLayer: this.uriWFS.name, - id: this.highlightedFeatureId, - }, - // tslint:disable-next-line:align - { highlight: false }); + this.changeFeatureState(this.highlightedFeatureId, { highlight: false }); + this.selectedFeature = null; this._panelState.next({ state: false }); } - get featuresToUpdate$(): Observable<void> { - return this._mapToUpdate.asObservable(); - } - get currentLayerType() { return this._currentLayerType; } - get mapUpdated$(): Observable<number> { - return this._mapUpdated.asObservable(); - } - set previousDatasetId(value: string) { this._previousDatasetId = value; } diff --git a/src/i18n/traductions.fr.ts b/src/i18n/traductions.fr.ts index a9491b3adfb659666a4d5db46e831cd2c0192aad..72c225c786a70a423440c966e718e902a45d7b40 100644 --- a/src/i18n/traductions.fr.ts +++ b/src/i18n/traductions.fr.ts @@ -239,7 +239,7 @@ export const datatsetDataRepresentationType = { long: { areal: 'Representation surfacique', linear: 'Representation linéaire', - punctual: 'Representation ponctuel', + punctual: 'Representation ponctuelle', }, short: { areal: 'Surfacique',