diff --git a/angular/projects/public-lng/src/app/applications/app-map/app-map.component.ts b/angular/projects/public-lng/src/app/applications/app-map/app-map.component.ts index afb28c6a3..c5367c7b4 100644 --- a/angular/projects/public-lng/src/app/applications/app-map/app-map.component.ts +++ b/angular/projects/public-lng/src/app/applications/app-map/app-map.component.ts @@ -43,6 +43,8 @@ const layers: { labels: null }; +const minimapLayers = { ...layers }; + const markerIcon = L.icon({ iconUrl: 'assets/images/baseline-location-24px.svg', // Retina Icon is not needed here considering we're using an SVG. Enable if you want to change to a raster asset. @@ -86,6 +88,11 @@ export class AppMapComponent implements AfterViewInit, OnDestroy { private isMapReady = false; private ngUnsubscribe: Subject = new Subject(); + private layersGroup = L.layerGroup(); + + private minimapCenter = [54.014438, -128.682595]; + private minimapZoom = 12; + private mapBaseLayerName = 'World Topographic'; readonly defaultBounds = L.latLngBounds([53.6, -129.5], [56.1, -120]); // all of BC @@ -182,10 +189,22 @@ export class AppMapComponent implements AfterViewInit, OnDestroy { } ); + // leaflet minimap needs its own layer to operate on to avoid "strange behaviour" + const Minimap_Topo_Map = L.tileLayer( + 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', + { + attribution: + // tslint:disable-next-line:max-line-length + 'Tiles © Esri — Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community', + maxZoom: 17.5, + noWrap: true + } + ); + this.map = L.map('map', { zoomControl: false, // will be added manually below - maxBounds: L.latLngBounds(L.latLng(-90, -180), L.latLng(90, 180)), // restrict view to "the world" - minZoom: 2, // prevent zooming out too far + maxBounds: L.latLngBounds(L.latLng(63, -141), L.latLng(46, -110)), // restrict view to "the world" + minZoom: 5, // prevent zooming out too far zoomSnap: 0.1, // for greater granularity when fitting bounds attributionControl: false, renderer: L.canvas({ tolerance: 15 }) @@ -243,8 +262,18 @@ export class AppMapComponent implements AfterViewInit, OnDestroy { const pipelineEndpoints = []; layers.facility = L.geoJSON(data.facility, { - style: { color: '#6092ff', weight: 2 } - }).addTo(this.map); + style: feature => { + return { color: '#6092ff', weight: 2 }; + }, + onEachFeature: (feature, featureLayer) => { + featureLayer.on('mouseover', e => { + this.ngOnLegendLngEnter(); + }); + featureLayer.on('mouseout', e => { + this.ngOnLegendLngLeave(); + }); + } + }).addTo(this.layersGroup); layers.pipeline = L.geoJSON(data.pipeline, { // style alternating segment colours @@ -274,15 +303,13 @@ export class AppMapComponent implements AfterViewInit, OnDestroy { $('#gas-button').css('background', '#ffffff'); }); } - }) - .bindTooltip( - (layer: any) => { - const p = layer.feature.properties; - return `Segment ${p.segment}`; - }, - { direction: 'center', offset: tooltipOffset, sticky: true } - ) - .addTo(this.map); + }).bindTooltip( + (layer: any) => { + const p = layer.feature.properties; + return `Segment ${p.segment}`; + }, + { direction: 'center', offset: tooltipOffset, sticky: true } + ).addTo(this.layersGroup); // Default marker style const markerOptions = { @@ -367,7 +394,7 @@ export class AppMapComponent implements AfterViewInit, OnDestroy { // classes imported from other files do not currently work here html: `${index + 1}` }) - }).addTo(this.map); + }).addTo(this.layersGroup); }); layers.facilities = L.geoJSON(data.facilities, { @@ -396,7 +423,7 @@ export class AppMapComponent implements AfterViewInit, OnDestroy { radius: 8, weight: 3, fillColor: '#a5ff82', - color: '#6092ff' + color: '#38598A' }); const popup = L.popup(popupOptions).setContent(lngPopup); featureLayer.bindPopup(popup); @@ -423,7 +450,7 @@ export class AppMapComponent implements AfterViewInit, OnDestroy { e.target.setStyle({ color: '#00f6ff' }); // Highlight geo feature if (feature.properties.LABEL === 'Kitimat M/S') { // Highlight legend entry - $('#lng-button').css('background', '#c4f9ff'); + this.ngOnLegendLngEnter(); } else { $('#gas-button').css('background', '#c4f9ff'); } @@ -433,22 +460,89 @@ export class AppMapComponent implements AfterViewInit, OnDestroy { e.target.setStyle({ color: '#38598A' }); // Unhighlight geo feature if (feature.properties.LABEL === 'Kitimat M/S') { // Unhighlight legend entry - $('#lng-button').css('background', '#ffffff'); + this.ngOnLegendLngLeave(); } else { $('#gas-button').css('background', '#ffffff'); } }); } - }) - .bindTooltip( - (layer: any) => { - return layer.feature.properties.LABEL; - }, - { direction: 'top', offset: tooltipOffset2 } - ) - .addTo(this.map); + }).bindTooltip( + (layer: any) => { + return layer.feature.properties.LABEL; + }, + { direction: 'top', offset: tooltipOffset2 } + ).addTo(this.layersGroup); + + this.layersGroup.addTo(this.map); }; + // make geoJSON data available for the minimap + const displayMiniMapData = data => { + minimapLayers.facility = L.geoJSON(data.facility, { + style: { color: '#6092ff', weight: 2 }, + onEachFeature: (feature, featureLayer) => { + featureLayer.on('mouseover', e => { + this.ngOnLegendLngEnter(); + }); + featureLayer.on('mouseout', e => { + this.ngOnLegendLngLeave(); + }); + } + }); + + minimapLayers.facilities = L.geoJSON(data.facilities, { + style: { color: '#6092ff', weight: 2 } + }); + + minimapLayers.pipeline = L.geoJSON(data.pipeline, { + style: { color: '#38598A', weight: 2 } + }); + + this.map.addControl(new MiniMapWrapper()); + }; + + // Add minimap. Modified from https://github.com/Norkart/Leaflet-MiniMap/ + const MiniMapWrapper = L.Control.extend({ + options: { + position: 'bottomleft', + layers: minimapLayers, + center: this.minimapCenter, + zoom: this.minimapZoom + }, + + onAdd: function(map) { + this._mainMap = map; + + this._container = L.DomUtil.create('div', 'leaflet-custom-minimap'); + this._container.title = 'Go to LNG Canada - Facility'; + this._container.onclick = () => this._mainMap.flyTo(this.options.center, this.options.zoom + 1.5); + L.DomEvent.disableClickPropagation(this._container); + L.DomEvent.on(this._container, 'mousewheel', L.DomEvent.stopPropagation); + + const minimapOptions = { + dragging: false, + zoomControl: false, + attributionControl: false, + doubleClickZoom: false, + minZoom: this.options.zoom, + maxZoom: this.options.zoom + }; + + this._miniMap = new L.Map(this._container, minimapOptions); + this.options.layers.facility.addTo(this._miniMap); + this.options.layers.pipeline.addTo(this._miniMap); + this._miniMap.addLayer(Minimap_Topo_Map); + + return this._container; + }, + + addTo: function(map) { + L.Control.prototype.addTo.call(this, map); + this._miniMap.setView(this.options.center, 12); + return this; + } + }); + // Data collection function const getIt = (loc: string, callback: any) => { $.get(loc) @@ -475,6 +569,7 @@ export class AppMapComponent implements AfterViewInit, OnDestroy { dataGeoJson.pipeline = topojson.feature(data[2], data[2].objects['pipeline-segments']); displayData(dataGeoJson); + displayMiniMapData(dataGeoJson); }; // Fetch all layer data in parallel @@ -511,6 +606,18 @@ export class AppMapComponent implements AfterViewInit, OnDestroy { } } + this.map.on('zoomstart', e => { + if (this.layersGroup && this.map.hasLayer(layers.facilities)) { + this.layersGroup.removeFrom(this.map); + } + }); + + this.map.on('zoomend', e => { + if (this.layersGroup && this.map.hasLayer(layers.facilities) === false) { + this.layersGroup.addTo(this.map); + } + }); + // save any future base layer changes this.map.on('baselayerchange', (e: L.LayersControlEvent) => { this.mapBaseLayerName = e.name; @@ -563,14 +670,29 @@ export class AppMapComponent implements AfterViewInit, OnDestroy { if (layer.feature.properties.LABEL === 'Kitimat M/S') { layer.setStyle({ color: '#00f6ff' }); } + minimapLayers.facility.eachLayer((minilayer: any) => { + minilayer.setStyle({ color: '#00f6ff' }); + }); + }); + layers.facility.eachLayer((layer: any) => { + layer.setStyle({ color: '#00f6ff' }); + }); + minimapLayers.facility.eachLayer((minilayer: any) => { + minilayer.setStyle({ color: '#00f6ff' }); }); } public ngOnLegendLngLeave() { $('#lng-button').css('background', '#ffffff'); layers.facilities.eachLayer((layer: any) => { if (layer.feature.properties.LABEL === 'Kitimat M/S') { - layer.setStyle({ color: '#6092ff' }); + layer.setStyle({ color: '#38598A' }); } + layers.facility.eachLayer((facilitylayer: any) => { + facilitylayer.setStyle({ color: '#6092ff' }); + }); + minimapLayers.facility.eachLayer((minilayer: any) => { + minilayer.setStyle({ color: '#6092ff' }); + }); }); } @@ -689,7 +811,7 @@ export class AppMapComponent implements AfterViewInit, OnDestroy { private fitBounds(bounds: L.LatLngBounds = null) { const fitBoundsOptions: L.FitBoundsOptions = { animate: false, - paddingTopLeft: [0, 100], + paddingTopLeft: [250, 100], paddingBottomRight: [0, 20] }; if (bounds && bounds.isValid()) { diff --git a/angular/projects/public-lng/src/assets/styles/components/leaflet.scss b/angular/projects/public-lng/src/assets/styles/components/leaflet.scss index 902b62da3..4b670efaf 100644 --- a/angular/projects/public-lng/src/assets/styles/components/leaflet.scss +++ b/angular/projects/public-lng/src/assets/styles/components/leaflet.scss @@ -124,3 +124,27 @@ .leaflet-top.leaflet-right .leaflet-control { margin-top: 14px; } + +.leaflet-custom-minimap{ + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.5), 0 6px 20px 0 rgba(0, 0, 0, 0.5); + width: 250px; + height:250px; + cursor: pointer; + border-radius: 10px; + border: 5px solid #38598A; +} + + +.leaflet-bottom .leaflet-custom-minimap{ +margin-bottom:10px; +} + +.leaflet-bottom{ + margin-bottom:33px; +} + +@media (max-width: 768px) { + .leaflet-custom-minimap { + display: none; + } +} \ No newline at end of file