@@ -79,6 +69,7 @@ FireIconMark.propTypes = {
lat: PropTypes.number.isRequired,
lon: PropTypes.number.isRequired,
scan: PropTypes.number,
+ track: PropTypes.number,
nasa: PropTypes.bool,
falsePositives: PropTypes.bool.isRequired,
industries: PropTypes.bool.isRequired,
diff --git a/imports/ui/components/Maps/FireListUnion.js b/imports/ui/components/Maps/FireListUnion.js
new file mode 100644
index 0000000..3b094c0
--- /dev/null
+++ b/imports/ui/components/Maps/FireListUnion.js
@@ -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 }) => (
));
+ return (
{items}
);
+}
+
+FireListUnion.propTypes = {
+ firesUnion: PropTypes.array.isRequired,
+ nasa: PropTypes.bool.isRequired,
+ history: PropTypes.object.isRequired,
+ t: PropTypes.func.isRequired
+};
diff --git a/imports/ui/components/Maps/FirePixel.js b/imports/ui/components/Maps/FirePixel.js
index c81b547..4efcabe 100644
--- a/imports/ui/components/Maps/FirePixel.js
+++ b/imports/ui/components/Maps/FirePixel.js
@@ -3,31 +3,32 @@
import React from 'react';
import { CircleMarker } from 'react-leaflet';
import PropTypes from 'prop-types';
-import FirePopup from './FirePopup';
import { translate } from 'react-i18next';
+import FirePopup from './FirePopup';
/* Less acurate (1 pixel per fire) but faster */
const FirePixel = ({
- lat, lon, nasa, id, when, t, history
+ lat, lon, nasa, id, when, t, history, track
}) => (
);
-
FirePixel.propTypes = {
lat: PropTypes.number.isRequired,
lon: PropTypes.number.isRequired,
id: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
+ scan: PropTypes.number,
+ track: PropTypes.number,
when: PropTypes.instanceOf(Date),
nasa: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired
diff --git a/imports/ui/components/Maps/FirePolygonMark.js b/imports/ui/components/Maps/FirePolygonMark.js
new file mode 100644
index 0000000..92e3640
--- /dev/null
+++ b/imports/ui/components/Maps/FirePolygonMark.js
@@ -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 (
+
+ );
+ /*
*/
+ }
+}
+
+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;
diff --git a/imports/ui/components/Maps/SubsUnion/SubsUnion.js b/imports/ui/components/Maps/SubsUnion/SubsUnion.js
index dab127a..41a4d81 100644
--- a/imports/ui/components/Maps/SubsUnion/SubsUnion.js
+++ b/imports/ui/components/Maps/SubsUnion/SubsUnion.js
@@ -33,8 +33,7 @@ const subsUnion = (union, options) => {
options.map.leafletElement.fitBounds(L.latLngBounds(bounds._northEast, bounds._southWest));
}
} else if (options.subs.length > 0) {
- const unionGroup = new L.FeatureGroup();
- const result = calcUnion(options.subs, unionGroup, sub => sub);
+ const result = calcUnion(L, options.subs, sub => sub, true);
const unionJson = result[0];
const bounds = result[1];
diff --git a/imports/ui/components/Maps/SubsUnion/Unify.js b/imports/ui/components/Maps/SubsUnion/Unify.js
index 46586b7..873dcbf 100644
--- a/imports/ui/components/Maps/SubsUnion/Unify.js
+++ b/imports/ui/components/Maps/SubsUnion/Unify.js
@@ -1,48 +1,9 @@
-import { check } from 'meteor/check';
-import LGeo from 'leaflet-geodesy';
-import tunion from '@turf/union';
-import ttrunc from '@turf/truncate';
+import { calcUnion as calcUnionWoBounds } from 'map-common-utils';
-// https://stackoverflow.com/questions/35394577/leaflet-js-union-merge-circles
-const truncOptions = { precision: 6, coordinates: 2 };
-
-const unify = (polyList) => {
- let unionTemp;
- for (let i = 0; i < polyList.length; i += 1) {
- const pol = polyList[i].toGeoJSON();
- const cleanPol = ttrunc(pol, truncOptions);
- if (i === 0) {
- unionTemp = cleanPol;
- } else {
- unionTemp = ttrunc(tunion(unionTemp, cleanPol), truncOptions);
- }
- }
- return unionTemp;
-};
-
-const calcUnion = (subs, group, decorated) => {
- const unionGroup = group;
- const copts = {
- parts: 144
- };
- subs.forEach((osub) => {
- try {
- if (osub.location && osub.location.lat && osub.location.lon && osub.distance) {
- check(osub.location.lon, Number);
- check(osub.location.lat, Number);
- check(osub.distance, Number);
- const dsub = decorated(osub);
- const circle = LGeo.circle([dsub.location.lat, dsub.location.lon], dsub.distance * 1000, copts);
- circle.addTo(unionGroup);
- } else {
- console.info(`Wrong subscription ${JSON.stringify(osub)}`);
- }
- } catch (e) {
- console.error(e, `Wrong subscription trying to make union ${JSON.stringify(osub)}`);
- }
- });
- const unionJson = unify(unionGroup.getLayers());
- return [unionJson, unionGroup.getBounds()];
+const calcUnion = (L, subs, decorated, typeCircle) => {
+ const unionJson = calcUnionWoBounds(subs, decorated, typeCircle);
+ // return [unionJson, L.geoJSON(unionJson).getBounds()];
+ return [unionJson, unionJson === null ? null : L.geoJSON(unionJson).getBounds()];
};
export default calcUnion;
diff --git a/imports/ui/components/NotificationsObserver/NotificationsObserver.js b/imports/ui/components/NotificationsObserver/NotificationsObserver.js
index e68b1ef..ac374b5 100644
--- a/imports/ui/components/NotificationsObserver/NotificationsObserver.js
+++ b/imports/ui/components/NotificationsObserver/NotificationsObserver.js
@@ -12,7 +12,7 @@ import { trim } from './util.js';
function process(notif) {
// No already notified
if (Push.Permission.has()) {
- if (!notif.webNotified) {
+ if (!notif.notified && notif.type === 'web') {
Push.create(i18n.t('AppName'), {
body: `${trim(notif.content)} (${i18n.t('fireDetected', { when: dateFromNow(notif.when) })})`,
icon: '/n-fire-marker.png',
@@ -47,7 +47,7 @@ Meteor.startup(() => {
if (Meteor.userId()) {
Meteor.subscribe('mynotifications');
// Check for notifications not processed at startup
- Notifications.find({ webNotified: null }).forEach((notif) => {
+ Notifications.find({ notified: null, type: 'web' }).forEach((notif) => {
process(notif);
});
}
diff --git a/imports/ui/layouts/App/App.js b/imports/ui/layouts/App/App.js
index d991262..4aeef3a 100644
--- a/imports/ui/layouts/App/App.js
+++ b/imports/ui/layouts/App/App.js
@@ -98,66 +98,66 @@ const App = props => (
/* https://react.i18next.com/components/i18nextprovider.html */
{props.i18nReady.get() &&
-
-
-
-
-
- { !props.loading &&
-
-
-
- {i18n.t('AppName')}
-
-
-
-
-
-
+
+
+
+
+
+ { !props.loading &&
+
+
+
+ {i18n.t('AppName')}
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
- {props.i18nReady.get() && }
- }
-
-
-
-
- }
+
+
+
+
+
+
+ {props.i18nReady.get() &&
}
+
}
+
+
+
+
+ }
);
@@ -186,7 +186,9 @@ export default withTracker(() => {
const loading = !Roles.subscription.ready() || !i18nReady.get();
const name = user && user.profile && user.profile.name && getUserName(user.profile.name);
const emailAddress = user && user.emails && user.emails[0].address;
- // console.log(`i18n ready?: ${i18nReady.get()}`);
+ Meteor.autorun(() => {
+ console.log(`i18n ready?: ${i18nReady.get()}`);
+ });
return {
loading,
loggingIn,
diff --git a/imports/ui/pages/Fires/Fires.js b/imports/ui/pages/Fires/Fires.js
index 0e4271a..2647992 100644
--- a/imports/ui/pages/Fires/Fires.js
+++ b/imports/ui/pages/Fires/Fires.js
@@ -10,7 +10,8 @@ import { Row, Col, Alert, FormGroup } from 'react-bootstrap';
import { Meteor } from 'meteor/meteor';
import { Bert } from 'meteor/themeteorchef:bert';
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 DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
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 el {{when}}', { when: this.dateLongFormat });
}
-
- return (fire && !loading ? (
+ const ready = fire && !loading;
+ const rect = ready ? rectangleAround({ lat: fire.lat, lon: fire.lon }, fire.track, fire.track) : null;
+ return (ready ? (
{t('AppName')}: {t('Información adicional sobre fuego')}
@@ -137,14 +139,13 @@ class Fire extends React.Component {
zoom={13}
>
- { this.circle = circle; this.handleLeafletCircleLoad(circle); }}
+ data={rect}
+ color="red"
+ stroke
+ width="1"
+ fillOpacity="0.0"
/>
{ (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, {{ count: this.props.activefires.length + this.props.firealerts.length }} fuegos activos. {{ countTotal: this.props.activefirestotal }} fuegos activos en el mundo.
+ En rojo, {{ count: 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 &&
@@ -307,6 +297,14 @@ class FiresMap extends React.Component {
neighbour={false}
industries={false}
/>
+ { Meteor.isDevelopment &&
+
+ }
{
// console.log(`With industries: ${withIndustries}`);
let subscription;
+ let subscriptionUnion;
let alertSubscription;
const centerStored = store.get('firesmap_center');
@@ -422,6 +423,14 @@ export default translate([], { wait: true })(withTracker(() => {
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,
@@ -442,6 +451,7 @@ export default translate([], { wait: true })(withTracker(() => {
});
Meteor.subscribe('activefirestotal');
+ Meteor.subscribe('activefiresuniontotal');
const settingsSubs = Meteor.subscribe('settings');
const lastCheck = SiteSettings.findOne({ name: 'last-fire-check' });
@@ -453,13 +463,15 @@ export default translate([], { wait: true })(withTracker(() => {
const lastFireDetected = ActiveFiresCollection.findOne({}, { sort: { when: -1 } });
return {
- loading: Meteor.status().status !== 'connected' || !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.ready()),
+ 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,
diff --git a/imports/ui/pages/Index/Index.js b/imports/ui/pages/Index/Index.js
index 5d6b130..9986fba 100644
--- a/imports/ui/pages/Index/Index.js
+++ b/imports/ui/pages/Index/Index.js
@@ -11,6 +11,7 @@ import PropTypes from 'prop-types';
// import { Link } from 'react-router-dom';
// https://www.npmjs.com/package/react-resize-detector
import ReactResizeDetector from 'react-resize-detector';
+import MobileStoreButton from 'react-mobile-store-button';
import _ from 'lodash';
import 'html5-device-mockups/dist/device-mockups.min.css';
import 'bootstrap-carousel-swipe/carousel-swipe';
@@ -20,7 +21,6 @@ import SubscriptionsMap from '/imports/ui/pages/Subscriptions/SubscriptionsMap';
import ShareIt from '/imports/ui/components/ShareIt/ShareIt';
import getFallbackLang from '/imports/modules/lang-fallback';
import FiresMap from '../FiresMap/FiresMap';
-
import './Index.scss';
import './Index-custom.scss';
@@ -102,6 +102,7 @@ class Index extends Component {
render() {
const { t } = this.props;
const title = `${t('AppName')}: ${t('Inicio')}`;
+ const androidUrl = 'https://play.google.com/store/apps/details?id=org.comunes.fires';
return (
@@ -181,6 +182,9 @@ class Index extends Component {
Siempre alerta a los fuegos en nuestro vecindario
{/* {this.props.t('Participa')} */}
+
+
+
diff --git a/imports/ui/pages/Index/Index.scss b/imports/ui/pages/Index/Index.scss
index 3fb18b4..527aa93 100644
--- a/imports/ui/pages/Index/Index.scss
+++ b/imports/ui/pages/Index/Index.scss
@@ -77,3 +77,7 @@
}
}
}
+
+.android-btn > div {
+ height: 50px !important;
+}
diff --git a/imports/ui/pages/Profile/Profile.js b/imports/ui/pages/Profile/Profile.js
index 665e9d7..6d9b24b 100644
--- a/imports/ui/pages/Profile/Profile.js
+++ b/imports/ui/pages/Profile/Profile.js
@@ -9,15 +9,17 @@ import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { Bert } from 'meteor/themeteorchef:bert';
import { testId } from '/imports/ui/components/Utils/TestUtils';
-import Col from '../../components/Col/Col';
-import { withTracker } from 'meteor/react-meteor-data';
-import InputHint from '../../components/InputHint/InputHint';
-import validate from '../../../modules/validate';
import { translate } from 'react-i18next';
import { T9n } from 'meteor-accounts-t9n';
+import { withTracker } from 'meteor/react-meteor-data';
+import Col from '../../components/Col/Col';
+import InputHint from '../../components/InputHint/InputHint';
+import validate from '../../../modules/validate';
import './Profile.scss';
+// List of languages: https://github.com/i18next/i18next/issues/1068
+
class Profile extends React.Component {
constructor(props) {
super(props);
@@ -155,6 +157,7 @@ class Profile extends React.Component {
renderPasswordUser(loading, user) {
const { t, i18n } = this.props;
+ const enabledLangs = ['en', 'es', 'gl'];
const langName = {
en: 'English', es: 'Español', gl: 'Galego', ast: 'Asturianu', ca: 'Català'
};
@@ -202,7 +205,7 @@ class Profile extends React.Component {
{langName[i18n.language]}
- {i18n.languages.map(lang => (
+ {enabledLangs.map(lang => (