Added fires union (wip)

This commit is contained in:
vjrj 2018-11-18 11:48:03 +01:00
parent 7b5f17fbe8
commit 6abe668740
12 changed files with 216 additions and 40 deletions

View file

@ -1,5 +1,6 @@
/* eslint-disable consistent-return */ /* eslint-disable consistent-return */
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo'; import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema'; import SimpleSchema from 'simpl-schema';
import firesCommonSchema from '../Common/FiresSchema'; import firesCommonSchema from '../Common/FiresSchema';
@ -18,8 +19,11 @@ ActiveFires.deny({
remove: () => true remove: () => true
}); });
const activeFiresSchema = firesCommonSchema;
ActiveFires.schema = new SimpleSchema(firesCommonSchema); activeFiresSchema.fireUnion = { type: Meteor.Collection.ObjectID, optional: true, blackbox: true };
ActiveFires.schema = new SimpleSchema(activeFiresSchema);
ActiveFires.attachSchema(ActiveFires.schema); ActiveFires.attachSchema(ActiveFires.schema);

View file

@ -0,0 +1,78 @@
/* global Counter */
/* eslint-disable import/no-absolute-path */
/* eslint-disable prefer-arrow-callback */
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { NumberBetween } from '/imports/modules/server/other-checks';
// import { whichAreFalsePositives, firesUnion } from '/imports/api/FalsePositives/server/publications';
// import FalsePositives from '/imports/api/FalsePositives/FalsePositives';
// import Industries from '/imports/api/Industries/Industries';
import ActiveFiresUnion from '../ActiveFiresUnion';
const counter = new Counter('countActiveFiresUnion', ActiveFiresUnion.find({}));
Meteor.publish('activefiresuniontotal', function total() {
return counter;
});
const activeFiresUnion = (b1, b2, c1, c2, withMarks) => {
// a --- b
// c --- d
const geometry = {
$geometry: {
type: 'Polygon',
coordinates: [[
[c1, c2],
[c1, b2],
[b1, b2],
[b1, c2],
[c1, c2]
]]
}
};
// console.log(JSON.stringify(geometry));
const fires = ActiveFiresUnion.find({
shape: {
$geoIntersects: geometry
}
}, {
fields: {
shape: 1,
centerid: 1,
when: 1,
createdAt: 1,
updatedAt: 1
}
});
if (Meteor.isDevelopment) console.log(`Active fires union total: ${fires.count()}`);
/*
if (withMarks && fires.fetch().length > 0) {
const union = firesUnion(fires);
const falsePos = whichAreFalsePositives(FalsePositives, union);
const industries = whichAreFalsePositives(Industries, union);
return [fires, falsePos, industries];
} */
return fires;
};
Meteor.publish('activefiresunionmyloc', function activeInMyLoc(northEastLng, northEastLat, southWestLng, southWestLat, withMarks) {
// latitude -90 and 90 and the longitude between -180 and 180
check(northEastLng, NumberBetween(-180, 180));
check(southWestLat, NumberBetween(-90, 90));
check(southWestLng, NumberBetween(-180, 180));
check(northEastLat, NumberBetween(-90, 90));
check(withMarks, Boolean);
if (!Meteor.isDevelopment) return this.ready(); // empty
return activeFiresUnion(northEastLng, northEastLat, southWestLng, southWestLat, withMarks);
});
// Warning: this increase always by one the fire stats
Meteor.publish('lastFireUnionDetected', function lastFireDetected() {
if (!Meteor.isDevelopment) return this.ready(); // empty
return ActiveFiresUnion.find({}, { limit: 1, sort: { when: -1 } });
});

View file

@ -9,6 +9,7 @@ import '../../api/Users/server/publications';
import '../../api/Utility/server/methods'; import '../../api/Utility/server/methods';
import '../../api/ActiveFires/server/publications'; import '../../api/ActiveFires/server/publications';
import '../../api/ActiveFiresUnion/server/publications';
import '../../api/FireAlerts/server/publications'; import '../../api/FireAlerts/server/publications';
import '../../api/Subscriptions/methods'; import '../../api/Subscriptions/methods';

View file

@ -1,10 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Circle } from 'react-leaflet'; import { GeoJSON } from 'react-leaflet';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { rectangleAround } from 'map-common-utils';
import { onMarkClick } from './MarkListeners'; import { onMarkClick } from './MarkListeners';
import FirePopup from './FirePopup'; import FirePopup from './FirePopup';
class FireCircleMark extends Component { class FireCircleMark extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -22,22 +22,26 @@ class FireCircleMark extends Component {
lat, lat,
lon, lon,
scan, scan,
track,
nasa, nasa,
id, id,
history, history,
when, when,
t t
} = this.props; } = this.props;
const rect = rectangleAround({ lat, lon }, track, track);
return ( return (
<Circle center={[lat, lon]} color="red" stroke={false} fillOpacity="1" fill radius={scan * 500} onClick={this.onClick}> <GeoJSON data={rect} color="red" stroke width="1" opacity=".4" fillOpacity=".3">
<FirePopup t={t} history={history} id={id} nasa={nasa} lat={lat} lon={lon} when={when} /> <FirePopup t={t} history={history} id={id} nasa={nasa} lat={lat} lon={lon} when={when} />
</Circle> </GeoJSON>
); );
/* <Circle center={[lat, lon]} color="red" stroke={false} fillOpacity="1" fill radius={scan * 500} onClick={this.onClick}> */
} }
} }
FireCircleMark.propTypes = { FireCircleMark.propTypes = {
scan: PropTypes.number.isRequired, scan: PropTypes.number.isRequired,
track: PropTypes.number.isRequired,
lat: PropTypes.number.isRequired, lat: PropTypes.number.isRequired,
lon: PropTypes.number.isRequired, lon: PropTypes.number.isRequired,
nasa: PropTypes.bool.isRequired, nasa: PropTypes.bool.isRequired,

View file

@ -3,8 +3,8 @@ import React, { Component, Fragment } from 'react';
import { CircleMarker, Marker, Tooltip } from 'react-leaflet'; import { CircleMarker, Marker, Tooltip } from 'react-leaflet';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { fireIconS, fireIconM, fireIconL, nFireIcon, industryIcon, regIndustryIcon } from '/imports/ui/components/Maps/Icons'; import { fireIconS, fireIconM, fireIconL, nFireIcon, industryIcon, regIndustryIcon } from '/imports/ui/components/Maps/Icons';
import { onMarkClick } from './MarkListeners';
import { translate } from 'react-i18next'; import { translate } from 'react-i18next';
import { onMarkClick } from './MarkListeners';
import FirePopup from './FirePopup'; import FirePopup from './FirePopup';
class FireIconMark extends Component { class FireIconMark extends Component {
@ -30,17 +30,7 @@ class FireIconMark extends Component {
render() { render() {
const { const {
lat, lat, lon, scan, track, nasa, id, history, falsePositives, industries, neighbour, when, t
lon,
scan,
nasa,
id,
history,
falsePositives,
industries,
neighbour,
when,
t
} = this.props; } = this.props;
return ( return (
<div> <div>
@ -79,6 +69,7 @@ FireIconMark.propTypes = {
lat: PropTypes.number.isRequired, lat: PropTypes.number.isRequired,
lon: PropTypes.number.isRequired, lon: PropTypes.number.isRequired,
scan: PropTypes.number, scan: PropTypes.number,
track: PropTypes.number,
nasa: PropTypes.bool, nasa: PropTypes.bool,
falsePositives: PropTypes.bool.isRequired, falsePositives: PropTypes.bool.isRequired,
industries: PropTypes.bool.isRequired, industries: PropTypes.bool.isRequired,

View file

@ -0,0 +1,22 @@
/* eslint-disable import/no-absolute-path */
/* eslint-disable react/jsx-indent-props */
/* eslint-disable react/jsx-indent */
import React from 'react';
import PropTypes from 'prop-types';
import FirePolygonMark from '/imports/ui/components/Maps/FirePolygonMark';
export default function FireListUnion(props) {
const {
firesUnion, nasa, t, history
} = props;
/* console.log(`Using marks: ${useMarks}, using pixels: ${usePixel}`); */
const items = firesUnion.map(({ _id, ...otherProps }) => (<FirePolygonMark t={t} history={history} id={_id} key={_id} nasa={nasa} {...otherProps} />));
return (<div style={{ display: 'none' }}>{items}</div>);
}
FireListUnion.propTypes = {
firesUnion: PropTypes.array.isRequired,
nasa: PropTypes.bool.isRequired,
history: PropTypes.object.isRequired,
t: PropTypes.func.isRequired
};

View file

@ -3,31 +3,32 @@
import React from 'react'; import React from 'react';
import { CircleMarker } from 'react-leaflet'; import { CircleMarker } from 'react-leaflet';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import FirePopup from './FirePopup';
import { translate } from 'react-i18next'; import { translate } from 'react-i18next';
import FirePopup from './FirePopup';
/* Less acurate (1 pixel per fire) but faster */ /* Less acurate (1 pixel per fire) but faster */
const FirePixel = ({ const FirePixel = ({
lat, lon, nasa, id, when, t, history lat, lon, nasa, id, when, t, history, track
}) => ( }) => (
<CircleMarker <CircleMarker
center={[lat, lon]} center={[lat, lon]}
color={nasa ? 'red' : '#D35400'} color={nasa ? 'red' : '#D35400'}
stroke={false} stroke={false}
fillOpacity="1" fillOpacity="1"
fill fill
radius={nasa ? 1 : 2} radius={nasa ? 1 : 2}
> >
<FirePopup t={t} history={history} id={id} nasa={nasa} lat={lat} lon={lon} when={when} /> <FirePopup t={t} history={history} id={id} nasa={nasa} lat={lat} lon={lon} when={when} />
</CircleMarker> </CircleMarker>
); );
FirePixel.propTypes = { FirePixel.propTypes = {
lat: PropTypes.number.isRequired, lat: PropTypes.number.isRequired,
lon: PropTypes.number.isRequired, lon: PropTypes.number.isRequired,
id: PropTypes.object.isRequired, id: PropTypes.object.isRequired,
history: PropTypes.object.isRequired, history: PropTypes.object.isRequired,
scan: PropTypes.number,
track: PropTypes.number,
when: PropTypes.instanceOf(Date), when: PropTypes.instanceOf(Date),
nasa: PropTypes.bool.isRequired, nasa: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired t: PropTypes.func.isRequired

View file

@ -0,0 +1,48 @@
import React, { Component } from 'react';
import { GeoJSON } from 'react-leaflet';
import PropTypes from 'prop-types';
import { onMarkClick } from './MarkListeners';
import FirePopup from './FirePopup';
class FirePolygonMark extends Component {
constructor(props) {
super(props);
this.t = props.t;
this.onClick = this.onClick.bind(this);
}
onClick() {
const { history, nasa, id } = this.props;
onMarkClick(history, nasa, id);
}
render() {
const {
nasa,
id,
shape,
centerid,
history,
when,
t
} = this.props;
const lon = centerid.coordinates[0];
const lat = centerid.coordinates[1];
return (
<GeoJSON data={shape} color="orange" stroke width="1" opacity=".2" fillOpacity=".0" />
);
/* <FirePopup t={t} history={history} id={id} nasa={nasa} lat={lat} lon={lon} when={when} /> */
}
}
FirePolygonMark.propTypes = {
id: PropTypes.object.isRequired,
nasa: PropTypes.bool.isRequired,
shape: PropTypes.object.isRequired,
centerid: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
when: PropTypes.instanceOf(Date).isRequired,
t: PropTypes.func.isRequired
};
export default FirePolygonMark;

View file

@ -10,7 +10,8 @@ import { Row, Col, Alert, FormGroup } from 'react-bootstrap';
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { Bert } from 'meteor/themeteorchef:bert'; import { Bert } from 'meteor/themeteorchef:bert';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { Map, Circle } from 'react-leaflet'; import { Map, GeoJSON } from 'react-leaflet';
import { rectangleAround } from 'map-common-utils';
import Blaze from 'meteor/gadicc:blaze-react-component'; import Blaze from 'meteor/gadicc:blaze-react-component';
import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers'; import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
import NotFound from '/imports/ui/pages/NotFound/NotFound'; import NotFound from '/imports/ui/pages/NotFound/NotFound';
@ -115,8 +116,9 @@ class Fire extends React.Component {
t('Información adicional sobre fuego detectado en {{where}} el {{when}}', { where: fire.address, when: this.dateLongFormat }) : t('Información adicional sobre fuego detectado en {{where}} el {{when}}', { where: fire.address, when: this.dateLongFormat }) :
t('Información adicional sobre fuego detectado el {{when}}', { when: this.dateLongFormat }); t('Información adicional sobre fuego detectado el {{when}}', { when: this.dateLongFormat });
} }
const ready = fire && !loading;
return (fire && !loading ? ( const rect = ready ? rectangleAround({ lat: fire.lat, lon: fire.lon }, fire.track, fire.track) : null;
return (ready ? (
<div className="ViewFire"> <div className="ViewFire">
<Helmet> <Helmet>
<title>{t('AppName')}: {t('Información adicional sobre fuego')}</title> <title>{t('AppName')}: {t('Información adicional sobre fuego')}</title>
@ -137,13 +139,13 @@ class Fire extends React.Component {
zoom={13} zoom={13}
> >
<Fragment> <Fragment>
<Circle <GeoJSON
center={[fire.lat, fire.lon]} data={rect}
color="red" color="red"
fillColor="red" stroke
fillOpacity={0.0} width="1"
interactive={false}
radius={fire.scan ? fire.scan * 500 : 300} fillOpacity="0.0"
ref={(circle) => { this.circle = circle; this.handleLeafletCircleLoad(circle); }} ref={(circle) => { this.circle = circle; this.handleLeafletCircleLoad(circle); }}
/> />
</Fragment> </Fragment>

View file

@ -23,9 +23,11 @@ import 'leaflet-sleep/Leaflet.Sleep.js';
import geolocation from '/imports/startup/client/geolocation'; import geolocation from '/imports/startup/client/geolocation';
import CenterInMyPosition from '/imports/ui/components/CenterInMyPosition/CenterInMyPosition'; import CenterInMyPosition from '/imports/ui/components/CenterInMyPosition/CenterInMyPosition';
import FireList from '/imports/ui/components/Maps/FireList'; 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 subsUnion from '/imports/ui/components/Maps/SubsUnion/SubsUnion';
import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers'; import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires'; import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion';
import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts'; import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
import IndustriesCollection, { industriesRemap } from '/imports/api/Industries/Industries'; import IndustriesCollection, { industriesRemap } from '/imports/api/Industries/Industries';
import FalsePositivesCollection, { falsePositivesRemap } from '/imports/api/FalsePositives/FalsePositives'; import FalsePositivesCollection, { falsePositivesRemap } from '/imports/api/FalsePositives/FalsePositives';
@ -202,6 +204,7 @@ class FiresMap extends React.Component {
if (Meteor.isDevelopment && this.props.activefires.length === 1) console.log(`Active fire: ${JSON.stringify(this.props.activefires[0])}`); 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 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(`False positives total: ${this.props.falsePositives.length}`);
if (Meteor.isDevelopment) console.log(`Fire alerts total: ${this.props.firealerts.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 (Meteor.isDevelopment) console.log(`Industries total: ${this.props.industries.length}`);
@ -307,6 +310,14 @@ class FiresMap extends React.Component {
neighbour={false} neighbour={false}
industries={false} industries={false}
/> />
{ Meteor.isDevelopment &&
<FireListUnion
t={t}
history={this.props.history}
firesUnion={this.props.activefiresunion}
nasa
/>
}
<FireList <FireList
t={t} t={t}
history={this.props.history} history={this.props.history}
@ -369,12 +380,14 @@ FiresMap.propTypes = {
userSubs: PropTypes.string, userSubs: PropTypes.string,
userSubsBounds: PropTypes.string, userSubsBounds: PropTypes.string,
activefires: PropTypes.arrayOf(PropTypes.object).isRequired, activefires: PropTypes.arrayOf(PropTypes.object).isRequired,
activefiresunion: PropTypes.arrayOf(PropTypes.object).isRequired,
lastFireDetected: PropTypes.object, lastFireDetected: PropTypes.object,
firealerts: PropTypes.arrayOf(PropTypes.object).isRequired, firealerts: PropTypes.arrayOf(PropTypes.object).isRequired,
falsePositives: PropTypes.arrayOf(PropTypes.object).isRequired, falsePositives: PropTypes.arrayOf(PropTypes.object).isRequired,
industries: PropTypes.arrayOf(PropTypes.object).isRequired, industries: PropTypes.arrayOf(PropTypes.object).isRequired,
lastCheck: PropTypes.instanceOf(Date), lastCheck: PropTypes.instanceOf(Date),
activefirestotal: PropTypes.number.isRequired, activefirestotal: PropTypes.number.isRequired,
activefiresuniontotal: PropTypes.number.isRequired,
center: PropTypes.arrayOf(PropTypes.number), center: PropTypes.arrayOf(PropTypes.number),
zoom: PropTypes.number, zoom: PropTypes.number,
marks: PropTypes.bool.isRequired, marks: PropTypes.bool.isRequired,
@ -391,6 +404,7 @@ export default translate([], { wait: true })(withTracker(() => {
// console.log(`With industries: ${withIndustries}`); // console.log(`With industries: ${withIndustries}`);
let subscription; let subscription;
let subscriptionUnion;
let alertSubscription; let alertSubscription;
const centerStored = store.get('firesmap_center'); const centerStored = store.get('firesmap_center');
@ -422,6 +436,14 @@ export default translate([], { wait: true })(withTracker(() => {
mapSize.get()[1].lat, mapSize.get()[1].lat,
marks.get() && zoom.get() >= MAXZOOM 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( alertSubscription = Meteor.subscribe(
'fireAlerts', 'fireAlerts',
mapSize.get()[0].lng, mapSize.get()[0].lng,
@ -442,6 +464,7 @@ export default translate([], { wait: true })(withTracker(() => {
}); });
Meteor.subscribe('activefirestotal'); Meteor.subscribe('activefirestotal');
Meteor.subscribe('activefiresuniontotal');
const settingsSubs = Meteor.subscribe('settings'); const settingsSubs = Meteor.subscribe('settings');
const lastCheck = SiteSettings.findOne({ name: 'last-fire-check' }); const lastCheck = SiteSettings.findOne({ name: 'last-fire-check' });
@ -453,13 +476,15 @@ export default translate([], { wait: true })(withTracker(() => {
const lastFireDetected = ActiveFiresCollection.findOne({}, { sort: { when: -1 } }); const lastFireDetected = ActiveFiresCollection.findOne({}, { sort: { when: -1 } });
return { return {
loading: Meteor.status().status !== 'connected' || !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.ready() && settingsSubs.ready()), loading: Meteor.status().status !== 'connected' || !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.ready() && settingsSubs.ready() && subscriptionUnion.ready()),
userSubs: userSubs ? userSubs.value : null, userSubs: userSubs ? userSubs.value : null,
userSubsBounds: userSubs ? userSubsBounds.value : null, userSubsBounds: userSubs ? userSubsBounds.value : null,
subsready: settingsSubs.ready(), subsready: settingsSubs.ready(),
// Not reactive query depending on zoom level // Not reactive query depending on zoom level
activefires: ActiveFiresCollection.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(), activefires: ActiveFiresCollection.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(),
activefiresunion: ActiveFiresUnion.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(),
activefirestotal: Counter.get('countActiveFires') + fireAlerts.length, activefirestotal: Counter.get('countActiveFires') + fireAlerts.length,
activefiresuniontotal: Counter.get('countActiveFiresUnion') + fireAlerts.length,
firealerts: fireAlerts, firealerts: fireAlerts,
falsePositives, falsePositives,
industries, industries,

6
package-lock.json generated
View file

@ -10835,9 +10835,9 @@
} }
}, },
"map-common-utils": { "map-common-utils": {
"version": "0.4.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/map-common-utils/-/map-common-utils-0.4.0.tgz", "resolved": "https://registry.npmjs.org/map-common-utils/-/map-common-utils-0.5.0.tgz",
"integrity": "sha512-GjLGyIfusloAGwtaP7H15yzwmrMoHYVvx3+L5xiDBpjbRsgMphaaw+r+in0phwuQZApmGoA+VWV6ttQfTWqwWw==", "integrity": "sha512-dldbEOEMxlrzSWg+OgSJQgODW58D4ZYNCYVH8+kml+UY8XEB6Rpo3FO4YkMPqIqU5XlBIadPad9N2Br89NBBOQ==",
"requires": { "requires": {
"@turf/bbox": "6.0.1", "@turf/bbox": "6.0.1",
"@turf/bbox-polygon": "6.0.1", "@turf/bbox-polygon": "6.0.1",

View file

@ -43,7 +43,7 @@
"leaflet-sleep": "^0.5.1", "leaflet-sleep": "^0.5.1",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"loms.perlin": "^1.0.1", "loms.perlin": "^1.0.1",
"map-common-utils": "^0.4.0", "map-common-utils": "^0.5.0",
"maxmind": "^2.3.0", "maxmind": "^2.3.0",
"meteor-accounts-t9n": "^2.0.3", "meteor-accounts-t9n": "^2.0.3",
"meteor-node-stubs": "^0.3.2", "meteor-node-stubs": "^0.3.2",