/* global Counter */ /* eslint-disable import/no-absolute-path */ /* eslint-disable react/jsx-indent-props */ /* eslint-disable react/jsx-indent */ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { ButtonGroup, Row, Col, Form } from 'react-bootstrap'; import { Meteor } from 'meteor/meteor'; import { ReactiveVar } from 'meteor/reactive-var'; import { withTracker } from 'meteor/react-meteor-data'; import { Tracker } from 'meteor/tracker'; import { Helmet } from 'react-helmet-async'; import { Trans, withTranslation } from 'react-i18next'; import { MapContainer } from 'react-leaflet'; import MapControl from '/imports/ui/components/Maps/MapControl'; import { MapReady, MapEvents } from '/imports/ui/components/Maps/MapBridge'; import LoadingBar from '/imports/ui/components/Loading/LoadingBar'; import _ from 'lodash'; import store from 'store'; import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; import 'leaflet-graphicscale/dist/Leaflet.GraphicScale.min.css'; import 'leaflet-graphicscale/dist/Leaflet.GraphicScale.min.js'; import 'leaflet-sleep/Leaflet.Sleep.js'; import geolocation from '/imports/startup/client/geolocation'; import CenterInMyPosition from '/imports/ui/components/CenterInMyPosition/CenterInMyPosition'; import FireList from '/imports/ui/components/Maps/FireList'; import FireListUnion from '/imports/ui/components/Maps/FireListUnion'; import subsUnion from '/imports/ui/components/Maps/SubsUnion/SubsUnion'; import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers'; import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires'; import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion'; import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts'; import IndustriesCollection, { industriesRemap } from '/imports/api/Industries/Industries'; import FalsePositivesCollection, { falsePositivesRemap } from '/imports/api/FalsePositives/FalsePositives'; import SiteSettings from '/imports/api/SiteSettings/SiteSettings'; import { isNotHomeAndMobile, isChrome, isAnyMobile } from '/imports/ui/components/Utils/isMobile'; import { isHome } from '/imports/ui/components/Utils/location'; import ShareIt from '/imports/ui/components/ShareIt/ShareIt'; import FullScreenMap from '/imports/ui/components/Maps/FullScreenMap'; import LocationAutocomplete from '/imports/ui/components/LocationAutocomplete/LocationAutocomplete'; import Gkeys from '/imports/startup/client/Gkeys'; import FireStats from './FireStats'; import './FiresMap.scss'; const MAXZOOM = 6; const MAXZOOMREACTIVE = 6; const DEFZOOM = 8; const zoom = new ReactiveVar(DEFZOOM); // https://en.wikipedia.org/wiki/Geographical_midpoint_of_Europe const center = new ReactiveVar([53.5775, 3.106111]); const mapSize = new ReactiveVar(); const marks = new ReactiveVar(true); const showUnion = new ReactiveVar(true); // Remove map in subscription class FiresMap extends React.Component { constructor(props) { super(props); this.state = { viewport: { center: props.center, zoom: props.zoom }, init: false, useMarkers: props.marks, scaleAdded: false, moving: false, showSubsUnion: props.showUnion }; const self = this; // viewportchange // https://stackoverflow.com/questions/23123138/perform-debounce-in-react-js this.debounceView = _.debounce((viewport) => { self.handleViewportChange(viewport); }, 1500); this.onViewportChanged = this.onViewportChanged.bind(this); this.onMoveEnd = this.onMoveEnd.bind(this); this.onMoveStart = this.onMoveStart.bind(this); this.handleMoveEnd = this.handleMoveEnd.bind(this); this.handleMapReady = this.handleMapReady.bind(this); } // v4: combined moveend/zoomend handler fed by — clears the // moving flag and reports the new viewport (was the v1 onMoveend + // onViewportChanged props on ). handleMoveEnd() { this.onMoveEnd(); if (!this.map) return; const c = this.map.getCenter(); this.onViewportChanged({ center: [c.lat, c.lng], zoom: this.map.getZoom() }); } // v4: MapReady hands us the ready Leaflet map (replaces ref/.leafletElement) handleMapReady(map) { this.map = map; this.handleLeafletLoad(map); } componentDidMount() { const self = this; Gkeys.load(() => { self.setState({ init: true }); }); } shouldComponentUpdate(nextProps, nextState) { const notMoving = !nextState.moving; return notMoving; } onMoveStart() { // this.setState({ moving: true }); this.state.moving = true; } onMoveEnd() { // this.setState({ moving: false }); this.state.moving = false; } onViewportChanged(viewport) { this.debounceView.cancel(); this.debounceView(viewport); } onAutocompleteChange(value) { this.handleViewportChange({ center: [value.lat, value.lng], zoom: DEFZOOM - 1 }); } getMap() { return this.map; } setShowSubsUnion(showSubsUnion) { this.setState({ showSubsUnion }); store.set('firesmap_showunion', showSubsUnion); } componentDidUnMount() { // this.setState({ init: true }); } handleViewportChange(viewport) { console.log(`Viewport changed: ${JSON.stringify(viewport)}`); if (this.map) { const bounds = this.getMap().getBounds(); // console.log(bounds); mapSize.set([bounds.getNorthEast(), bounds.getSouthWest()]); store.set('firesmap_center', viewport.center); store.set('firesmap_zoom', viewport.zoom); if (viewport.zoom > this.state.viewport.zoom) { this.state.viewport = viewport; if (Meteor.isDevelopment) console.log('Don\'t query we are in the same point'); return; } zoom.set(viewport.zoom); center.set(viewport.center); this.state.viewport = viewport; } } centerOnUserLocation(viewport) { this.setState({ viewport: { center: viewport.center, zoom: 10 } }); // v4 map is uncontrolled: move it imperatively if (this.map) this.map.setView(viewport.center, 10); } useMarkers(use) { this.setState({ useMarkers: use }); store.set('firesmap_marks', use); marks.set(use); } addScale(map) { // https://www.npmjs.com/package/leaflet-graphicscale const options = { fill: 'fill', showSubunits: true }; L.control.graphicScale([options]).addTo(map); } handleLeafletLoad(map) { if (map && !this.state.moving) { const lmap = map; try { const bounds = lmap.getBounds(); mapSize.set([bounds.getNorthEast(), bounds.getSouthWest()]); if (!this.state.scaleAdded) { this.addScale(lmap); this.state.scaleAdded = true; } } catch (e) { console.warn('Failed to set map bounds and scale'); } this.state.union = subsUnion(this.state.union, { map, subs: this.props.userSubs, show: this.state.showSubsUnion, bounds: this.props.userSubsBounds, fromServer: true, fit: false }); } } render() { const { t } = this.props; const loading = this.props.loading || !this.props.subsready; console.log(`Rendering ${loading ? 'loading' : 'LOADED'}, zoom ${this.state.viewport.zoom}, map ${this.props.activefires.length + this.props.firealerts.length} of ${this.props.activefirestotal} total. False positives: ${this.props.falsePositives.length}. Reactive ${this.state.viewport.zoom >= MAXZOOMREACTIVE}`); const title = `${t('AppName')}: ${t('Fuegos activos')}`; if (Meteor.isDevelopment && this.props.activefires.length === 1) console.log(`Active fire: ${JSON.stringify(this.props.activefires[0])}`); if (Meteor.isDevelopment) console.log(`Active fires total: ${this.props.activefires.length}`); if (Meteor.isDevelopment) console.log(`Active fires union total: ${this.props.activefiresunion.length}`); if (Meteor.isDevelopment) console.log(`False positives total: ${this.props.falsePositives.length}`); if (Meteor.isDevelopment) console.log(`Fire alerts total: ${this.props.firealerts.length}`); if (Meteor.isDevelopment) console.log(`Industries total: ${this.props.industries.length}`); if (!this.state.init) { return
; } return ( /* Large number of markers: https://stackoverflow.com/questions/43015854/large-dataset-of-markers-or-dots-in-leaflet/43019740#43019740 */
{ this.divElement = divElement; }} > { !isHome() && {title} }

Fuegos activos

{isNotHomeAndMobile() && this.onAutocompleteChange(value)} /> this.setShowSubsUnion(e.target.checked)} label={Resaltar en verde el área vigilada por nuestros usuarios/as (*)} /> {(this.state.viewport.zoom >= MAXZOOM) && this.useMarkers(e.target.checked)} label={Resaltar los fuegos con un marcador} />} }

{ (this.props.activefires.length + this.props.firealerts.length) === 0 ? No hay fuegos activos en esta zona del mapa. {{ countTotal: this.props.activefirestotal }} fuegos activos en el mundo. : En rojo, {{ inMap: this.props.activefiresunion.length + this.props.firealerts.length }} fuegos activos. {{ countTotal: this.props.activefirestotal }} fuegos activos en el mundo. }

{isNotHomeAndMobile() && this.props.firealerts.length > 0 &&

En naranja, los fuegos notificados por nuestros usuarios/as recientemente.

} { /* disabled */ false && isNotHomeAndMobile() && this.props.firealerts.length === 0 && !isAnyMobile &&

No hay fuegos notificados recientemente por nuestros usuarios/as en esta zona.

}

{ this.state.viewport.zoom >= MAXZOOMREACTIVE ? Los fuegos activos se actualizan en tiempo cuasi real. : Haga zoom en una zona de su interés si quiere que los fuegos se actualicen en tiempo real. }

{loading ? : ''} {/* https://github.com/CliffCloud/Leaflet.Sleep */} {/* http://wiki.openstreetmap.org/wiki/Tile_servers */} {!this.props.loading && = MAXZOOM} useMarkers={this.state.useMarkers} nasa={false} falsePositives neighbour={false} industries={false} /> { Meteor.isDevelopment && } = MAXZOOM} useMarkers={this.state.useMarkers} nasa={false} neighbour={false} falsePositives={false} industries /> = MAXZOOM} useMarkers={this.state.useMarkers} nasa neighbour={false} falsePositives={false} industries={false} /> = MAXZOOM} useMarkers={this.state.useMarkers} nasa={false} neighbour falsePositives={false} industries={false} /> } this.centerOnUserLocation(viewport)} onlyIcon {... this.props} />

(*)Para preservar la privacidad de nuestros usuarios/as, los datos reflejados están aleatoriamente alterados y son solo orientativos.

Nota: Las nubes pueden entorpecer la detección de fuegos activos.

{ !isHome() && }
); } } FiresMap.propTypes = { loading: PropTypes.bool.isRequired, subsready: PropTypes.bool.isRequired, userSubs: PropTypes.string, userSubsBounds: PropTypes.string, activefires: PropTypes.arrayOf(PropTypes.object).isRequired, activefiresunion: PropTypes.arrayOf(PropTypes.object).isRequired, lastFireDetected: PropTypes.object, firealerts: PropTypes.arrayOf(PropTypes.object).isRequired, falsePositives: PropTypes.arrayOf(PropTypes.object).isRequired, industries: PropTypes.arrayOf(PropTypes.object).isRequired, lastCheck: PropTypes.instanceOf(Date), activefirestotal: PropTypes.number.isRequired, activefiresuniontotal: PropTypes.number.isRequired, center: PropTypes.arrayOf(PropTypes.number), zoom: PropTypes.number, marks: PropTypes.bool.isRequired, showUnion: PropTypes.bool.isRequired, history: PropTypes.object.isRequired, t: PropTypes.func.isRequired }; let geoInit = true; export default withTranslation()(withTracker(() => { // const firesType = match.params.type; // const withIndustries = firesType === 'with-industries'; // console.log(`With industries: ${withIndustries}`); let subscription; let subscriptionUnion; let alertSubscription; const centerStored = store.get('firesmap_center'); const zoomStored = store.get('firesmap_zoom'); const marksStored = store.get('firesmap_marks'); const showUnionStored = store.get('firesmap_showunion'); zoom.set(zoomStored || DEFZOOM); center.set(centerStored || [0, 0]); if (typeof marksStored === 'boolean') { marks.set(marksStored); } if (typeof showUnionStored === 'boolean') { showUnion.set(showUnionStored); } // Disable, because this increase the number of fires by one // Meteor.subscribe('lastFireDetected'); Tracker.autorun(() => { if ((centerStored !== [0, 0] || geolocation.get()) && geoInit) { center.set(centerStored || geolocation.get()); // console.log(`Geolocation ${geolocation.get()}`); geoInit = false; } if (mapSize.get() && mapSize.get()[0].lng && mapSize.get()[1].lat) { subscription = Meteor.subscribe( 'activefiresmyloc', mapSize.get()[0].lng, mapSize.get()[0].lat, mapSize.get()[1].lng, mapSize.get()[1].lat, marks.get() && zoom.get() >= MAXZOOM ); subscriptionUnion = Meteor.subscribe( 'activefiresunionmyloc', mapSize.get()[0].lng, mapSize.get()[0].lat, mapSize.get()[1].lng, mapSize.get()[1].lat, false ); alertSubscription = Meteor.subscribe( 'fireAlerts', mapSize.get()[0].lng, mapSize.get()[0].lat, mapSize.get()[1].lng, mapSize.get()[1].lat ); /* if (withIndustries) { Meteor.subscribe( 'industriesMyloc', mapSize.get()[0].lng, mapSize.get()[0].lat, mapSize.get()[1].lng, mapSize.get()[1].lat ); } */ } }); Meteor.subscribe('activefirestotal'); Meteor.subscribe('activefiresuniontotal'); const settingsSubs = Meteor.subscribe('settings'); const lastCheck = SiteSettings.findOne({ name: 'last-fire-check' }); const userSubs = SiteSettings.findOne({ name: 'subs-public-union' }); const userSubsBounds = SiteSettings.findOne({ name: 'subs-public-union-bounds' }); const fireAlerts = FireAlertsCollection.find().fetch(); const falsePositives = FalsePositivesCollection.find().fetch().map(falsePositivesRemap); const industries = IndustriesCollection.find().fetch().map(industriesRemap); const lastFireDetected = ActiveFiresCollection.findOne({}, { sort: { when: -1 } }); return { loading: Meteor.status().status !== 'connected' || !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.ready() && settingsSubs.ready() && subscriptionUnion.ready()), userSubs: userSubs ? userSubs.value : null, userSubsBounds: userSubs ? userSubsBounds.value : null, subsready: settingsSubs.ready(), // Not reactive query depending on zoom level activefires: ActiveFiresCollection.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(), activefiresunion: ActiveFiresUnion.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(), activefirestotal: Counter.get('countActiveFires') + fireAlerts.length, activefiresuniontotal: Counter.get('countActiveFiresUnion') + fireAlerts.length, firealerts: fireAlerts, falsePositives, industries, lastCheck: lastCheck ? lastCheck.value : null, lastFireDetected, center: center.get(), marks: marks.get(), showUnion: showUnion.get(), zoom: zoom.get() }; })(FiresMap));