Added geolocation. Improved map subs. FireMap page

This commit is contained in:
vjrj 2017-12-01 21:18:10 +01:00
parent 0b4f895506
commit fe7a314d92
13 changed files with 179 additions and 59 deletions

View file

@ -41,3 +41,5 @@ mixmax:smart-disconnect # Disconnect on lost focus
flowkey:raven # js errors flowkey:raven # js errors
davidsichau:piwik # Stats davidsichau:piwik # Stats
thebakery:ipgeocoder thebakery:ipgeocoder
mdg:geolocation
natestrauser:publish-performant-counts

View file

@ -57,6 +57,7 @@ launch-screen@1.1.1
livedata@1.0.18 livedata@1.0.18
localstorage@1.1.1 localstorage@1.1.1
logging@1.1.17 logging@1.1.17
mdg:geolocation@1.3.0
meteor@1.7.2 meteor@1.7.2
meteor-base@1.1.0 meteor-base@1.1.0
minifier-css@1.2.16 minifier-css@1.2.16
@ -70,6 +71,7 @@ modules-runtime@0.8.0
mongo@1.2.2 mongo@1.2.2
mongo-dev-server@1.0.1 mongo-dev-server@1.0.1
mongo-id@1.0.6 mongo-id@1.0.6
natestrauser:publish-performant-counts@0.1.2
npm-bcrypt@0.9.3 npm-bcrypt@0.9.3
npm-mongo@2.2.30 npm-mongo@2.2.30
oauth@1.1.13 oauth@1.1.13

View file

@ -2,8 +2,10 @@ import { Meteor } from 'meteor/meteor';
import { check, Match } from 'meteor/check'; import { check, Match } from 'meteor/check';
import ActiveFires from '../ActiveFires'; import ActiveFires from '../ActiveFires';
Meteor.publish('activefires', function activefires() { var counter = new Counter('countActiveFires', ActiveFires.find({}));
return ActiveFires.find();
Meteor.publish('activefirestotal', function() {
return counter;
}); });
const validZoom = Match.Where((zoom) => { const validZoom = Match.Where((zoom) => {
@ -30,7 +32,6 @@ var NullOr = function (type) {
}); });
}; };
const zoomMetersPerPixel = [156412, 78206, 39103, 19551, 9776, 4888, 2444, 1222, 610.984, 305.492, 152.746, 76.373, 38.187, 19.093, 9.547, 4.773, 2.387, 1.193, 0.596, 0.298]; const zoomMetersPerPixel = [156412, 78206, 39103, 19551, 9776, 4888, 2444, 1222, 610.984, 305.492, 152.746, 76.373, 38.187, 19.093, 9.547, 4.773, 2.387, 1.193, 0.596, 0.298];
var activefires = function(zoom, lat, lng) { var activefires = function(zoom, lat, lng) {

View file

@ -5,6 +5,7 @@ import Cache from 'i18next-localstorage-cache';
import { T9n } from 'meteor-accounts-t9n'; import { T9n } from 'meteor-accounts-t9n';
import en from 'meteor-accounts-t9n/build/en'; import en from 'meteor-accounts-t9n/build/en';
import es from 'meteor-accounts-t9n/build/es'; import es from 'meteor-accounts-t9n/build/es';
import moment from 'moment';
// Adapted from: https://github.com/appigram/ryfma-boilerplate/blob/44c1eabfb9928b5623afab36a23997969e5beb02/imports/startup/client/i18n.js // Adapted from: https://github.com/appigram/ryfma-boilerplate/blob/44c1eabfb9928b5623afab36a23997969e5beb02/imports/startup/client/i18n.js
@ -61,6 +62,15 @@ i18n.use(backend)
}, },
interpolation: { interpolation: {
escapeValue: false, // not needed for react!! escapeValue: false, // not needed for react!!
formatSeparator: ",",
format: function(value, format, lng) {
// https://www.i18next.com/formatting.html
// console.log(`Value: ${value} with format: ${format} to lang: ${lng}`);
if (format === 'uppercase') return value.toUpperCase();
if (value instanceof Date) return moment(value).format(format);
if (format === 'number') return Intl.NumberFormat(lng).format(value);
return value;
}
}, },
whitelist: false, whitelist: false,
// whitelist: ['es', 'en'], // allowed languages // whitelist: ['es', 'en'], // allowed languages
@ -84,6 +94,10 @@ i18n.use(backend)
} }
}, function(err, t) { }, function(err, t) {
// initialized and ready to // initialized and ready to
if (err) {
console.error(err);
return;
}
document.title = t("AppName"); document.title = t("AppName");
// Accounts translation // Accounts translation
@ -111,4 +125,9 @@ i18n.use(backend)
CookieConsent.init(cookiesOpt); CookieConsent.init(cookiesOpt);
}); });
i18n.on('languageChanged', function(lng) {
moment.locale(lng);
T9n.setLanguage(lng);
});
export default i18n; export default i18n;

View file

@ -12,7 +12,10 @@ import { translate } from 'react-i18next';
import NavItem from '../NavItem/NavItem'; import NavItem from '../NavItem/NavItem';
const PublicNavigation = (props) => ( const PublicNavigation = (props) => (
<Nav pullRight> <Nav pullRight>
<LinkContainer className="nav-item" anchorClassName="nav-link" to="/fires">
<NavItem href="/fires">{props.t('activeFires')}</NavItem>
</LinkContainer>
<LinkContainer className="nav-item" anchorClassName="nav-link" to="/signup"> <LinkContainer className="nav-item" anchorClassName="nav-link" to="/signup">
<NavItem eventKey={1} href="/signup">{props.t('Registrarse')}</NavItem> <NavItem eventKey={1} href="/signup">{props.t('Registrarse')}</NavItem>
</LinkContainer> </LinkContainer>

View file

@ -39,6 +39,7 @@ import ravenLogger from '/imports/startup/client/ravenLogger';
import Blaze from 'meteor/gadicc:blaze-react-component'; import Blaze from 'meteor/gadicc:blaze-react-component';
import createHistory from 'history/createBrowserHistory'; import createHistory from 'history/createBrowserHistory';
import { check } from 'meteor/check'; import { check } from 'meteor/check';
import FiresMap from '../../pages/FiresMap/FiresMap';
import './App.scss'; import './App.scss';
@ -63,6 +64,7 @@ const App = props => (
<Authenticated exact path="/documents/:_id" component={ViewDocument} {...props} /> <Authenticated exact path="/documents/:_id" component={ViewDocument} {...props} />
<Authenticated exact path="/documents/:_id/edit" component={EditDocument} {...props} /> <Authenticated exact path="/documents/:_id/edit" component={EditDocument} {...props} />
<Authenticated exact path="/profile" component={Profile} {...props} /> <Authenticated exact path="/profile" component={Profile} {...props} />
<Route path="/fires" component={FiresMap} {...props} />
<Public path="/signup" component={Signup} {...props} /> <Public path="/signup" component={Signup} {...props} />
<Public path="/login" component={Login} {...props} /> <Public path="/login" component={Login} {...props} />
<Route path="/logout" component={Logout} {...props} /> <Route path="/logout" component={Logout} {...props} />

View file

@ -1,13 +1,15 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import { Row } from 'react-bootstrap'; import { Row, Button } from 'react-bootstrap';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { translate } from 'react-i18next'; import { Trans, Interpolate, translate } from 'react-i18next';
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import { Circle, CircleMarker, Map, Marker, Popup, TileLayer, PropTypes as MapPropTypes } from 'react-leaflet' import { Circle, CircleMarker, Map, Marker, Popup, TileLayer, PropTypes as MapPropTypes } from 'react-leaflet'
import ActiveFiresCollection from '../../../api/ActiveFires/ActiveFires'; import ActiveFiresCollection from '../../../api/ActiveFires/ActiveFires';
import { withTracker } from 'meteor/react-meteor-data'; import { withTracker } from 'meteor/react-meteor-data';
import Loading from '../../components/Loading/Loading'; import Loading from '../../components/Loading/Loading';
import './FiresMap.scss';
const MyPopupMarker = ({ children, position }) => ( const MyPopupMarker = ({ children, position }) => (
<Marker position={position}> <Marker position={position}>
@ -90,6 +92,8 @@ const DEFAULT_VIEWPORT = {
zoom: 8, zoom: 8,
} }
class FiresMap extends React.Component { class FiresMap extends React.Component {
constructor(props) { constructor(props) {
@ -100,6 +104,26 @@ class FiresMap extends React.Component {
} }
} }
centerOnUserLocation = () => {
// https://atmospherejs.com/mdg/geolocation
// https://stackoverflow.com/questions/31608579/somethings-wrong-with-my-meteor-geolocation-functions
var userGeoLocation = new ReactiveVar(null);
var state = this.state;
Tracker.autorun(function (computation) {
userGeoLocation.set(Geolocation.latLng());
if (userGeoLocation.get()) {
//stop the tracker if we got something
computation.stop();
console.log(userGeoLocation.get());
state.viewport = {
center: [userGeoLocation.get().lat, userGeoLocation.get().lng],
zoom: 11
}
}
});
}
onViewportChanged = viewport => { onViewportChanged = viewport => {
// console.log(this.state.viewport); // console.log(this.state.viewport);
zoom.set(viewport.zoom); zoom.set(viewport.zoom);
@ -122,45 +146,66 @@ class FiresMap extends React.Component {
} }
return ( return (
/* !this.props.loading ?*/
/* Large number of markers: /* Large number of markers:
https://stackoverflow.com/questions/43015854/large-dataset-of-markers-or-dots-in-leaflet/43019740#43019740 */ https://stackoverflow.com/questions/43015854/large-dataset-of-markers-or-dots-in-leaflet/43019740#43019740 */
<div> <div>
<Row>Fires active in this map: {this.props.activefires.length}</Row> {this.props.loading ?
<Row>Mostrar fábricas</Row> <Row className="align-items-center justify-content-center">
<Map <Loading />
animate={true} </Row>
preferCanvas={false} :""}
onClick={this.onClickReset} <h4 className="page-header"><Trans parent="span">Fuegos activos</Trans></h4>
viewport={this.state.viewport} <Row>
onViewportChanged={this.onViewportChanged} <Map
> animate={true}
{/* http://wiki.openstreetmap.org/wiki/Tile_servers */} preferCanvas={false}
{/* <TileLayer onClick={this.onClickReset}
attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors" viewport={this.state.viewport}
url="http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png" onViewportChanged={this.onViewportChanged}
/> */} >
{/* <TileLayer {/* http://wiki.openstreetmap.org/wiki/Tile_servers */}
attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors" {/* <TileLayer
url="http://{s}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png" attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
/> */} url="http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png"
<TileLayer /> */}
attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors" {/* <TileLayer
url="http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png" attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
/> url="http://{s}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png"
{/* <TileLayer /> */}
attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors" <TileLayer
url="https://{s}.tiles.mapbox.com/v3/americanredcross.hcji22de/{z}/{x}/{y}.png" attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
/> url="http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png"
<Circle center={position} color="red" fill={true} radius={scan*1000} /> />
<MyCirclesList circles={circles} />*/} {/* <TileLayer
attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
url="https://{s}.tiles.mapbox.com/v3/americanredcross.hcji22de/{z}/{x}/{y}.png"
/>
<Circle center={position} color="red" fill={true} radius={scan*1000} />
<MyCirclesList circles={circles} />*/}
<FireList activefires={this.props.activefires} scale={this.state.viewport.zoom > 8} /> <FireList activefires={this.props.activefires} scale={this.state.viewport.zoom > 8} />
{/* <MyMarkersList markers={markers} /> */} {/* <MyMarkersList markers={markers} /> */}
</Map> </Map>
</Row>
<Row>
<p>
<Interpolate i18nKey="activeFireInMapCount"
count={this.props.activefires.length}
countTotal={this.props.activefirestotal}
></Interpolate>
</p>
<p>
<em><Trans parent="span">Fuentes: NASA y alertas vecinales de nuestr@s usuari@s</Trans></em>
</p>
</Row>
<Row>
<Button bsStyle="default" onClick={() => this.centerOnUserLocation()}>
<i className="location"/>
<Trans className="location" parent="span">Centrar el mapa en tu ubicación</Trans>
</Button>
</Row>
</div> </div>
/* : <Loading />*/
); );
} }
} }
@ -173,9 +218,11 @@ const lng = new ReactiveVar(DEF_LNG);
FiresMap.propTypes = { FiresMap.propTypes = {
loading: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired,
activefires: PropTypes.arrayOf(PropTypes.object).isRequired, activefires: PropTypes.arrayOf(PropTypes.object).isRequired,
activefirestotal: PropTypes.number.isRequired,
viewport: PropTypes.object.isRequired
}; };
export default withTracker(() => { export default translate([], { wait: true }) (withTracker(() => {
var subscription; var subscription;
Meteor.autorun(function() { Meteor.autorun(function() {
// Subscribe for the current templateId (only if one is selected). Note this // Subscribe for the current templateId (only if one is selected). Note this
@ -185,7 +232,7 @@ export default withTracker(() => {
// TODO select position // TODO select position
subscription = Meteor.subscribe('activefiresmyloc', zoom.get(), lat.get(), lng.get()); subscription = Meteor.subscribe('activefiresmyloc', zoom.get(), lat.get(), lng.get());
}); });
Meteor.subscribe('activefirestotal');
// const subscription = Meteor.subscribe('activefiresmyloc', zoom.get()); // const subscription = Meteor.subscribe('activefiresmyloc', zoom.get());
Meteor.call("geo", function (error, response) { Meteor.call("geo", function (error, response) {
if (error) { if (error) {
@ -198,11 +245,10 @@ export default withTracker(() => {
return { return {
loading: !subscription.ready(), loading: !subscription.ready(),
activefires: ActiveFiresCollection.find().fetch(), activefires: ActiveFiresCollection.find().fetch(),
geoip: geoip.get(), activefirestotal: Counter.get('countActiveFires'),
zoom: zoom.get(),
viewport: { viewport: {
center: geoip.get(), // a point in the sea center: geoip.get(), // a point in the sea
zoom: zoom.get(), zoom: zoom.get(),
} }
}; };
})(FiresMap); })(FiresMap));

View file

@ -0,0 +1,40 @@
/* https://github.com/PaulLeCam/react-leaflet/issues/108 */
.leaflet-container {
height: 100%;
min-height: 75vh;
width: 100%;
min-width: 75vw;
display: flex;
margin: 30px auto;
}
.Loading {
position: absolute;
top: calc(50% - 150px);
left: calc(50% - 150px);
z-index: 1000;
}
.Loading > svg {
width: 300px;
height: 300px;
stroke: grey;
}
@media only screen and (max-width: 400px) {
/* .leaflet-container {
height: 500px;
width: 85vw;
} */
}
i.location {
background: url(/my_location.svg) no-repeat top left;
background-size: contain;
cursor: pointer;
display: inline-block;
height: 30px;
width: 35px;
vertical-align: middle;
}

View file

@ -140,11 +140,3 @@
color: white; color: white;
text-transform: uppercase; text-transform: uppercase;
} }
/* https://github.com/PaulLeCam/react-leaflet/issues/108 */
.leaflet-container {
height: 500px;
width: 85%;
margin: 0 auto;
}

View file

@ -5,7 +5,6 @@ import {render} from 'react-dom';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
// https://www.npmjs.com/package/react-resize-detector // https://www.npmjs.com/package/react-resize-detector
import ReactResizeDetector from 'react-resize-detector'; import ReactResizeDetector from 'react-resize-detector';
import FiresMap from './FiresMap';
import './Index.scss'; import './Index.scss';
import './Index-custom.scss'; import './Index-custom.scss';
@ -120,11 +119,6 @@ class Index extends Component {
<Link className="participe-btn btn btn-lg btn-warning" role="button" to="/signup">{this.props.t('Participa')}</Link> <Link className="participe-btn btn btn-lg btn-warning" role="button" to="/signup">{this.props.t('Participa')}</Link>
</div> </div>
</section> </section>
<section className="py-5">
<a id="fires" name="fires"></a>
<FiresMap />
</section>
</div> </div>
); );
}; };

View file

@ -41,3 +41,10 @@ $todos-palette2: $todos-palette2b;
$todos-palette3: $todos-palette3b; $todos-palette3: $todos-palette3b;
$todos-palette4: $todos-palette4b; $todos-palette4: $todos-palette4b;
$todos-palette5: $todos-palette5b; $todos-palette5: $todos-palette5b;
// It seems that we are not using bootstrap scss version so does not work
$primary: $todos-palette1;
$success: $todos-palette2;
$info: $todos-palette3;
$warning: $todos-palette4;
$danger: #d9534f;

View file

@ -107,5 +107,13 @@
"Utilizamos cookies para asegurar un mejor uso de nuestra web. Si continúas navegando, consideramos que aceptas su uso": "Utilizamos cookies para asegurar un mejor uso de nuestra web. Si continúas navegando, consideramos que aceptas su uso":
"Utilizamos cookies para asegurar un mejor uso de nuestra web. Si continúas navegando, consideramos que aceptas su uso", "Utilizamos cookies para asegurar un mejor uso de nuestra web. Si continúas navegando, consideramos que aceptas su uso",
"Participa": "Participa":
"Participa" "Participa",
"activeFires":
"Fuegos activos",
"Fuegos activos":
"Fuegos activos",
"activeFireInMapCount":
"Fuegos activos en el mapa {{count,number}} de un total de {{countTotal,number}} fuegos activos detectados en el mundo.",
"Centrar el mapa en tu ubicación":
"Centrar el mapa en tu ubicación"
} }

4
public/my_location.svg Normal file
View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
<path d="M0 0h48v48h-48z" fill="none"/>
<path d="M24 16c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm17.88 6c-.92-8.34-7.54-14.96-15.88-15.88v-4.12h-4v4.12c-8.34.92-14.96 7.54-15.88 15.88h-4.12v4h4.12c.92 8.34 7.54 14.96 15.88 15.88v4.12h4v-4.12c8.34-.92 14.96-7.54 15.88-15.88h4.12v-4h-4.12zm-17.88 16c-7.73 0-14-6.27-14-14s6.27-14 14-14 14 6.27 14 14-6.27 14-14 14z"/>
</svg>

After

Width:  |  Height:  |  Size: 478 B