From 56a7468ae36929cb44e1dfe82cd8aacffea7b9a0 Mon Sep 17 00:00:00 2001 From: vjrj Date: Fri, 15 Dec 2017 17:43:45 +0100 Subject: [PATCH] Improved firesmap --- .../api/ActiveFires/server/publications.js | 62 +++++--- imports/startup/server/IPGeocoder.js | 45 ++++-- imports/ui/components/Maps/FireList.js | 20 +-- imports/ui/components/Maps/FirePixel.js | 11 +- .../ui/components/Navigation/Navigation.js | 62 ++++---- imports/ui/pages/FiresMap/FiresMap.js | 132 +++++++++++------- imports/ui/pages/Sandbox/Sandbox.js | 14 +- 7 files changed, 214 insertions(+), 132 deletions(-) diff --git a/imports/api/ActiveFires/server/publications.js b/imports/api/ActiveFires/server/publications.js index d0cf1eb..ac2ff81 100644 --- a/imports/api/ActiveFires/server/publications.js +++ b/imports/api/ActiveFires/server/publications.js @@ -1,15 +1,17 @@ /* global Counter */ /* eslint-disable import/no-absolute-path */ +/* eslint-disable prefer-arrow-callback */ import { Meteor } from 'meteor/meteor'; import { check, Match } from 'meteor/check'; -import IPGeocoder from '/imports/startup/server/IPGeocoder'; +import { localize } from '/imports/startup/server/IPGeocoder'; import ActiveFires from '../ActiveFires'; const counter = new Counter('countActiveFires', ActiveFires.find({})); - -Meteor.publish('activefirestotal', () => counter); +Meteor.publish('activefirestotal', function total() { + return counter; +}); const validZoom = Match.Where((zoom) => { // http://wiki.openstreetmap.org/wiki/Zoom_levels @@ -54,9 +56,9 @@ const activefires = (zoom, lat, lng, height, width) => { const distUnt = resolution * Math.max(height, width); const distance = Math.trunc(distUnt); // console.log(`so ${height}x${width} gives ${Math.trunc(resolution*height/1000)} x ${Math.trunc(resolution*width/1000)} km, so looking in ${distance}`); - console.log(`so ${height}x${width} gives ${resolution} of resolution, so looking in ${distance}`); + console.log(`So ${height}x${width} gives ${Math.trunc(resolution)} of resolution, so looking in ${Math.trunc(distance / 1000)}km`); - return ActiveFires.find({ + const fires = ActiveFires.find({ ourid: { $near: { $geometry: { @@ -74,9 +76,42 @@ const activefires = (zoom, lat, lng, height, width) => { scan: 1 } }); + console.log(`Fires total: ${fires.count()}`); + return fires; }; -Meteor.publish('activefiresmyloc', (zoom, lat, lng, height, width) => { + +Meteor.publish('allActiveFires', function allActive() { + // latitude -90 and 90 and the longitude between -180 and 180 + + const { latitude, longitude } = localize().location; + console.log(`${latitude}, ${longitude}`); + check(latitude, NumberBetween(-90, 90)); + check(longitude, NumberBetween(-180, 180)); + // https://docs.meteor.com/api/collections.html#Mongo-Collection-find + return ActiveFires.find({ + ourid: { + $near: { + $geometry: { + type: 'Point', + coordinates: [longitude, latitude] + }, + $minDistance: 0, + $maxDistance: 1000 // 156412000 + } + } + }, { + fields: { + _id: 0, + lat: 1, + lon: 1, + scan: 1 + }, + maxTimeMs: 30000 + }); +}); + +Meteor.publish('activefiresmyloc', function activeInMyLoc(zoom, lat, lng, height, width) { check(zoom, validZoom); check(lat, NullOr(Number)); check(lng, NullOr(Number)); @@ -84,19 +119,8 @@ Meteor.publish('activefiresmyloc', (zoom, lat, lng, height, width) => { check(width, NullOr(Number)); console.log(`Check active fires in ${lat},${lng} with zoom ${zoom} pixels in ${height}x${width} map`); if (lat === null || lng === null) { - let clientIP; - if (this.connection && this.connection.clientAddress) { - clientIP = this.connection.clientAddress; - } else { - console.warn('We cannot get this meteor connection IP'); - clientIP = '127.0.0.1'; - } - if (clientIP === '127.0.0.1') { - clientIP = '80.58.61.250'; // Some Spain IP address - } - // https://www.npmjs.com/package/maxmind - const location = IPGeocoder.get(clientIP); - console.log(location); + const location = localize(); + console.log(`${location.latitude}, ${location.longitude}`); return activefires(zoom, location.latitude, location.longitude, height, width); } return activefires(zoom, lat, lng, height, width); diff --git a/imports/startup/server/IPGeocoder.js b/imports/startup/server/IPGeocoder.js index cf0daa5..6cae62b 100644 --- a/imports/startup/server/IPGeocoder.js +++ b/imports/startup/server/IPGeocoder.js @@ -1,6 +1,13 @@ +/* eslint-disable prefer-arrow-callback */ + import { Meteor } from 'meteor/meteor'; import maxmind from 'maxmind'; +process.env.HTTP_FORWARDED_COUNT = Meteor.settings.private.proxies_count; +if (!Meteor.isDevelopment) { + console.log(`Number or proxies (needed for client IP lookup): ${process.env.HTTP_FORWARDED_COUNT}`); +} + // https://stackoverflow.com/questions/13969655/how-do-you-check-whether-the-given-ip-is-internal-or-not function isPrivateIP(ip) { const parts = ip.split('.'); @@ -13,22 +20,30 @@ function isPrivateIP(ip) { const IPGeocoder = maxmind.openSync(`${process.env.PWD}/private/GeoLite2-City.mmdb`); export default IPGeocoder; +// Warning: Meteor cannot access to this.connection with arrow functions +export function localize() { + // https://stackoverflow.com/questions/14843232/how-to-get-the-user-ip-address-in-meteor-server/22657421#22657421 + let clientIP; + if (this.connection && this.connection.clientAddress) { + clientIP = this.connection.clientAddress; + } else { + console.warn(`We cannot get this meteor connection IP for this connection (${this.connection})`); + clientIP = '127.0.0.1'; + } + if (isPrivateIP(clientIP)) { + clientIP = '80.58.61.250'; // Some Spain IP address + } + // console.log(`Geolocating ${clientIP}`); + + // TODO: cron download GeoLite-City + // http://dev.maxmind.com/geoip/geoip2/geolite2/ + const location = IPGeocoder.get(clientIP); + // console.log(location); + return location; +} + Meteor.methods({ - geo() { - // https://stackoverflow.com/questions/14843232/how-to-get-the-user-ip-address-in-meteor-server/22657421#22657421 - let clientIP = this.connection.clientAddress; - - if (isPrivateIP(clientIP)) { - clientIP = '80.58.61.250'; // Some Spain IP address - } - // console.log(`Geolocating ${clientIP}`); - - // TODO: cron download GeoLite-City - // http://dev.maxmind.com/geoip/geoip2/geolite2/ - const location = IPGeocoder.get(clientIP); - // console.log(location); - return location; - }, + geo: localize, getMapKey() { // http://meteorpedia.com/read/Environment_Variables // https://developers.google.com/maps/documentation/javascript/get-api-key diff --git a/imports/ui/components/Maps/FireList.js b/imports/ui/components/Maps/FireList.js index 3616ade..60d8b3e 100644 --- a/imports/ui/components/Maps/FireList.js +++ b/imports/ui/components/Maps/FireList.js @@ -11,15 +11,17 @@ export default function FireList(props) { const { fires, scale, useMarkers, nasa } = props; - const items = fires.map(({ _id, ...otherProps }) => { - if (useMarkers && scale) { - return (); - } - if (!nasa && !scale) { - return (); - } - return (); - }); + const useMarks = useMarkers && scale; + const usePixel = !nasa || !scale; + /* console.log(`Using marks: ${useMarks}, using pixels: ${usePixel}`); */ + let items; + if (useMarks) { + items = fires.map(({ _id, ...otherProps }) => ()); + } else if (usePixel) { + items = fires.map(({ _id, ...otherProps }) => ()); + } else { + items = fires.map(({ _id, ...otherProps }) => ()); + } return (
{items}
); } diff --git a/imports/ui/components/Maps/FirePixel.js b/imports/ui/components/Maps/FirePixel.js index e480d33..57f2092 100644 --- a/imports/ui/components/Maps/FirePixel.js +++ b/imports/ui/components/Maps/FirePixel.js @@ -1,10 +1,19 @@ +/* eslint-disable react/jsx-indent-props */ + import React from 'react'; import { CircleMarker } from 'react-leaflet'; import PropTypes from 'prop-types'; /* Less acurate (1 pixel per fire) but faster */ const FirePixel = ({ lat, lon, nasa }) => ( - + ); diff --git a/imports/ui/components/Navigation/Navigation.js b/imports/ui/components/Navigation/Navigation.js index 033d76c..bf34328 100644 --- a/imports/ui/components/Navigation/Navigation.js +++ b/imports/ui/components/Navigation/Navigation.js @@ -13,38 +13,38 @@ import './Navigation.scss'; const Navigation = props => ( ); diff --git a/imports/ui/pages/FiresMap/FiresMap.js b/imports/ui/pages/FiresMap/FiresMap.js index b8683ab..bcd8916 100644 --- a/imports/ui/pages/FiresMap/FiresMap.js +++ b/imports/ui/pages/FiresMap/FiresMap.js @@ -31,13 +31,11 @@ import './FiresMap.scss'; const { BaseLayer } = LayersControl; const MAXZOOM = 6; +const MAXZOOMREACTIVE = 6; const zoom = new ReactiveVar(8); -const lat = new ReactiveVar(); -const lng = new ReactiveVar(); -const height = new ReactiveVar(400); -const width = new ReactiveVar(400); +const center = new ReactiveVar([null, null]); +const mapSize = new ReactiveVar([400, 400]); -// TODO share only the used part of fires data // Remove map in subscription class FiresMap extends React.Component { constructor(props) { @@ -62,11 +60,26 @@ class FiresMap extends React.Component { Gkeys.load((err, key) => { self.setState({ gkey: key }); }); - height.set(this.divElement.clientHeight); - width.set(this.divElement.clientWidth); + mapSize.set([this.divElement.clientHeight, this.divElement.clientWidth]); this.addScale(); } + /* componentWillReceiveProps(nextProps) { + * if (nextProps.loading) { + * // console.log('Loading new fires'); + * } + * // this.setState({ loading: nextProps.loading }); + * } + */ + + /* shouldComponentUpdate(nextProps, nextState) { + * if (nextProps.loading) { + * return true; // false; + * } + * return true; + * } + */ + onViewportChanged(viewport) { this.debounceView(viewport); } @@ -81,7 +94,6 @@ class FiresMap extends React.Component { } setShowSubsUnion(show) { - this.setState({ showSubsUnion: show }); this.showSubsUnion(show); } @@ -115,35 +127,38 @@ class FiresMap extends React.Component { handleViewportChange(viewport) { console.log(`Viewport changed: ${JSON.stringify(viewport)}`); + if (viewport.center === this.state.viewport.center && + viewport.zoom === this.state.viewport.zoom) { + // Do nothing, in same point + return; + } zoom.set(viewport.zoom); - lat.set(viewport.center[0]); - lng.set(viewport.center[1]); + center.set(viewport.center); this.setState({ viewport }); - /* this.state.viewport = viewport; - * this.state.modified = true; */ if (this.props.subsready && this.fireMap) { this.showSubsUnion(this.state.showSubsUnion); } } centerOnUserLocation(viewport) { - this.handleViewportChange(viewport); + this.setState({ viewport }); + // this.handleViewportChange(viewport); } useMarkers(use) { this.setState({ useMarkers: use }); - // this.state.useMarkers = use; - // this.forceUpdate(); } addScale() { - // https://www.npmjs.com/package/leaflet-graphicscale - const map = this.getMap(); - const options = { - fill: 'fill', - showSubunits: true - }; - L.control.graphicScale([options]).addTo(map); + if (this.fireMap) { + // https://www.npmjs.com/package/leaflet-graphicscale + const map = this.getMap(); + const options = { + fill: 'fill', + showSubunits: true + }; + L.control.graphicScale([options]).addTo(map); + } } render() { @@ -151,7 +166,7 @@ class FiresMap extends React.Component { // Show union of users this.showSubsUnion(this.state.showSubsUnion); } - console.log('Rendering map'); + console.log(`Rendering ${this.props.loading ? 'loading' : 'LOADED'} map ${this.props.activefires.length} of ${this.props.activefirestotal} total. Subs users ready ${this.props.subsready}, reactive ${this.state.viewport.zoom >= MAXZOOMREACTIVE}`); const { t } = this.props; const osmlayer = ( @@ -175,10 +190,10 @@ class FiresMap extends React.Component {

- { this.props.activefires.length === 0 ? - No hay fuegos activos en esta zona del mapa. Hay un total de {{ countTotal: this.props.activefirestotal }} fuegos activos detectados en todo el mundo. : - En rojo, {{ count: this.props.activefires.length }} fuegos activos en el mapa. Hay un total de {{ countTotal: this.props.activefirestotal }} fuegos activos detectados en todo el mundo por la NASA. - } + { this.props.activefires.length === 0 ? + No hay fuegos activos en esta zona del mapa. Hay un total de {{ countTotal: this.props.activefirestotal }} fuegos activos detectados en todo el mundo. : + En rojo, {{ count: this.props.activefires.length }} fuegos activos en el mapa. Hay un total de {{ countTotal: this.props.activefirestotal }} fuegos activos detectados en todo el mundo por la NASA. + }

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

@@ -188,17 +203,25 @@ class FiresMap extends React.Component { {(this.state.viewport.zoom >= MAXZOOM) && this.useMarkers(e.target.checked)}> - Resaltar los fuegos con un marcador + Resaltar los fuegos con un marcador } this.centerOnUserLocation(viewport)} /> +

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

{/* https://github.com/CliffCloud/Leaflet.Sleep */} + { this.fireMap = map; }} animate - // minZoom={4} + minZoom={5} preferCanvas onClick={this.onClickReset} viewport={this.state.viewport} @@ -212,32 +235,34 @@ class FiresMap extends React.Component { sleepOpacity={0.6} > {/* http://wiki.openstreetmap.org/wiki/Tile_servers */} + {!this.props.loading && = MAXZOOM} useMarkers={this.state.useMarkers} nasa - /> + />} + {!this.props.loading && + />} {osmlayer} { this.state.gkey && - - - } + + + } { this.state.gkey && - - - } + + + } { this.state.gkey && - - - } + + + } @@ -261,13 +286,21 @@ FiresMap.propTypes = { export default translate([], { wait: true })(withTracker(() => { let subscription; + let init = true; Meteor.autorun(() => { - if (geolocation.get()) { - lat.set(geolocation.get()[0]); - lng.set(geolocation.get()[1]); + if (geolocation.get() && init) { + center.set(geolocation.get()); + init = false; } - if (zoom.get() || lat.get() || lng.get()) { - subscription = Meteor.subscribe('activefiresmyloc', zoom.get(), lat.get(), lng.get(), height.get(), width.get()); + if (mapSize.get()) { + subscription = Meteor.subscribe( + 'activefiresmyloc', + zoom.get(), + center.get()[0], + center.get()[1], + mapSize.get()[0], + mapSize.get()[1] + ); } }); @@ -276,18 +309,21 @@ export default translate([], { wait: true })(withTracker(() => { Meteor.subscribe('fireAlerts'); const userSubs = Meteor.subscribe('userSubsToFires'); // const subscription = Meteor.subscribe('activefiresmyloc', zoom.get()); - console.log(`Active fires ${ActiveFiresCollection.find().fetch().length} of ${Counter.get('countActiveFires')}`); + // Warning with the performance of this log: + // console.log(`Active fires ${ActiveFiresCollection.find().count()} of ${Counter.get('countActiveFires')}`); // console.log(`Active neighborhood fires ${FireAlertsCollection.find().fetch().length} and users subscribed ${UserSubsToFiresCollection.find().fetch().length}`); // console.log(UserSubsToFiresCollection.find().fetch()); return { loading: !subscription.ready(), + userSubs: UserSubsToFiresCollection.find().fetch(), subsready: userSubs.ready(), - activefires: ActiveFiresCollection.find().fetch(), + // Not reactive query depending on zoom level + activefires: ActiveFiresCollection.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(), + // activefires: ActiveFiresCollection.find({}).fetch(), activefirestotal: Counter.get('countActiveFires'), firealerts: FireAlertsCollection.find().fetch().map(doc => ( { _id: doc._id, lat: doc.location.lat, lon: doc.location.lon } )), - userSubs: UserSubsToFiresCollection.find().fetch(), viewport: { center: geolocation.get(), zoom: zoom.get() diff --git a/imports/ui/pages/Sandbox/Sandbox.js b/imports/ui/pages/Sandbox/Sandbox.js index 8837c6a..c8d0de0 100644 --- a/imports/ui/pages/Sandbox/Sandbox.js +++ b/imports/ui/pages/Sandbox/Sandbox.js @@ -1,22 +1,18 @@ +/* eslint-disable import/no-absolute-path */ import React from 'react'; // import PropTypes from 'prop-types'; import { translate } from 'react-i18next'; class Sandbox extends React.Component { - constructor(props) { - super(props); - /* this.state = { - * init: false - * }; */ - } - componentDidMount() { // this.setState({init: true}); } render() { return ( -
+
+
+
); } } @@ -25,4 +21,4 @@ Sandbox.propTypes = { // history: PropTypes.object.isRequired }; -export default translate([], { wait: true }) (Sandbox); +export default translate([], { wait: true })(Sandbox);