Better fire stats. False positives mark api part

This commit is contained in:
Vicente J. Ruiz Jurado 2018-08-08 08:55:31 +02:00
parent e9c6c3fc55
commit cb02156ae1
10 changed files with 174 additions and 75 deletions

View file

@ -7,12 +7,12 @@ import 'fireMarkType.dart';
class FireMarker extends Marker {
FireMarker(location, type,
FireMarker(LatLng pos, type,
[onTap = null, AnchorPos anchor = AnchorPos.center, Anchor anchorOverride])
: super(
width: 80.0,
height: 80.0,
point: new LatLng(location.lat, location.lon),
point: pos,
builder: (ctx) => new Container(
child: new GestureDetector(
child: new FireMarkerIcon(type), onTap: onTap)

View file

@ -1,7 +1,6 @@
import 'dart:core';
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/basicLocation.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
@ -18,12 +17,13 @@ import 'fireMarker.dart';
import 'generated/i18n.dart';
import 'genericMapBottom.dart';
import 'globals.dart' as globals;
import 'layerSelectorMapPlugin.dart';
import 'models/appState.dart';
import 'models/fireMapState.dart';
import 'redux/actions.dart';
import 'slider.dart';
import 'zoomMapPlugin.dart';
import 'layerSelectorMapPlugin.dart';
@immutable
class _ViewModel {
final String serverUrl;
@ -36,6 +36,7 @@ class _ViewModel {
final OnLocationEditConfirm onEditConfirm;
final OnLocationEditCancel onEditCancel;
final OnLocationEditing onEditing;
final OnFalsePositive onFalsePositive;
_ViewModel(
{@required this.mapState,
@ -47,6 +48,7 @@ class _ViewModel {
@required this.onEdit,
@required this.onEditing,
@required this.onEditConfirm,
@required this.onFalsePositive,
@required this.onEditCancel});
@override
@ -108,6 +110,8 @@ class _genericMapState extends State<genericMap> {
store.dispatch(new UpdateYourLocationMapAction(loc));
store.dispatch(new EditConfirmYourLocationAction(loc));
},
onFalsePositive: (notif, type) => store
.dispatch(new MarkFireAsFalsePositiveAction(notif, type)),
serverUrl: store.state.serverUrl,
mapState: store.state.fireMapState);
},
@ -127,8 +131,16 @@ class _genericMapState extends State<genericMap> {
MapOptions mapOptions = new MapOptions(
center: new LatLng(_location.lat, _location.lon),
plugins: globals.isDevelopment
? [new ZoomMapPlugin(), new AttributionPlugin(), new LayerSelectorMapPlugin()]
: [new DummyMapPlugin(), new AttributionPlugin(), new LayerSelectorMapPlugin()],
? [
new ZoomMapPlugin(),
new AttributionPlugin(),
new LayerSelectorMapPlugin()
]
: [
new DummyMapPlugin(),
new AttributionPlugin(),
new LayerSelectorMapPlugin()
],
// this works ?
interactive: false,
zoom: 13.0,
@ -166,8 +178,8 @@ class _genericMapState extends State<genericMap> {
switch (layer) {
case FireMapLayer.osmc:
case FireMapLayer.osmcGrey:
attribution = '© OpenStreetMap contributors';
break;
attribution = '© OpenStreetMap contributors';
break;
case FireMapLayer.esri:
case FireMapLayer.esriSatellite:
case FireMapLayer.esriTerrain:
@ -177,20 +189,24 @@ class _genericMapState extends State<genericMap> {
switch (layer) {
case FireMapLayer.osmc:
subdomains = ['a', 'b', 'c'];
baseLayer = 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png';
baseLayer =
'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png';
break;
case FireMapLayer.osmcGrey:
subdomains = ['a', 'b', 'c'];
baseLayer = 'https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png';
baseLayer = 'https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png';
break;
case FireMapLayer.esri:
baseLayer = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}';
baseLayer =
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}';
break;
case FireMapLayer.esriSatellite:
baseLayer = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}';
baseLayer =
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}';
break;
case FireMapLayer.esriTerrain:
baseLayer = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}';
baseLayer =
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}';
}
FlutterMap map = new FlutterMap(
options: mapOptions,
@ -209,7 +225,7 @@ class _genericMapState extends State<genericMap> {
: new DummyMapPluginOptions(),
new MarkerLayerOptions(
markers: buildMarkers(
_location,
mapState.status == FireMapStatus.viewFireNotification ? new LatLng(mapState.fireNotification.lat, mapState.fireNotification.lon): new LatLng(_location.lat, _location.lon),
mapState.fires,
mapState.industries,
mapState.falsePos,
@ -290,9 +306,11 @@ class _genericMapState extends State<genericMap> {
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
bottomNavigationBar: new GenericMapBottom(
onSave: () => view.onEditConfirm(_location),
onCancel: () => view.onEditCancel(_initialLocation),
state: view.mapState,
onSave: () => view.onEditConfirm(_location),
onCancel: () => view.onEditCancel(_initialLocation),
onFalsePositive: (sealed, type) =>
view.onFalsePositive(sealed, type),
state: view.mapState,
scaffoldKey: _scaffoldKey,
),
body: LayoutBuilder(
@ -362,28 +380,30 @@ class _genericMapState extends State<genericMap> {
}
}
List<Marker> buildMarkers(YourLocation yourLocation, List<dynamic> fires,
List<dynamic> falsePos, List<dynamic> industries, bool isNotif) {
List<Marker> buildMarkers(LatLng pos, List<dynamic> fires,
List<dynamic> falsePosList, List<dynamic> industries, bool isNotif) {
List<Marker> markers = [];
print('building markers: fires: ${fires.length} falsePos: ${falsePosList.length} industries: ${industries.length}, isNotif: ${isNotif} ');
const calibrate = false; // useful when we change the fire icons size
markers.add(FireMarker(
yourLocation, isNotif ? FireMarkType.fire : FireMarkType.position));
if (calibrate) markers.add(FireMarker(yourLocation, FireMarkType.pixel));
falsePos.forEach((fire) {
var coords = fire['geo']['coordinates'];
var loc = BasicLocation(lon: coords[0], lat: coords[1]);
pos, isNotif ? FireMarkType.fire : FireMarkType.position));
if (calibrate) markers.add(FireMarker(pos, FireMarkType.pixel));
falsePosList.forEach((falsePos) {
var coords = falsePos['geo']['coordinates'];
print('false pos: ${coords}');
var loc = LatLng(coords[1], coords[0]);
markers.add(FireMarker(loc, FireMarkType.falsePos));
if (calibrate) markers.add(FireMarker(loc, FireMarkType.pixel));
});
industries.forEach((fire) {
industries.forEach((industry) {
// print(fire['geo']['coordinates']);
var coords = fire['geo']['coordinates'];
var loc = BasicLocation(lon: coords[0], lat: coords[1]);
var coords = industry['geo']['coordinates'];
var loc = LatLng(coords[1], coords[0]);
markers.add(FireMarker(loc, FireMarkType.industry));
if (calibrate) markers.add(FireMarker(loc, FireMarkType.pixel));
});
fires.forEach((fire) {
var loc = new BasicLocation(lat: fire['lat'], lon: fire['lon']);
var loc = new LatLng(fire['lat'], fire['lon']);
markers.add(
FireMarker(loc, FireMarkType.fire, () => print('marker pressed')));
markers.add(FireMarker(loc, FireMarkType.pixel));

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:flutter/material.dart';
import 'colors.dart';
@ -14,17 +15,19 @@ import 'models/fireMapState.dart';
typedef void OnSave();
typedef void OnCancel();
typedef void OnFalsePositive();
typedef void OnFalsePositive(FireNotification notif, FalsePositiveType type);
class GenericMapBottom extends StatelessWidget {
final OnSave onSave;
final OnCancel onCancel;
final OnFalsePositive onFalsePositive;
final FireMapState state;
final GlobalKey<ScaffoldState> scaffoldKey;
GenericMapBottom(
{@required this.onSave,
@required this.onCancel,
@required this.onFalsePositive,
@required this.state,
@required this.scaffoldKey});
@ -107,7 +110,7 @@ class GenericMapBottom extends StatelessWidget {
value: value, child: new Text(menuText));
}).toList(),
onChanged: (value) async {
// FIXME api call
onFalsePositive(state.fireNotification, value);
await new Future.delayed(
new Duration(milliseconds: 500));
scaffoldKey.currentState.showSnackBar(new SnackBar(

View file

@ -1,8 +1,14 @@
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:meta/meta.dart';
enum FireMapStatus { view, subscriptionConfirm, unsubscribe, edit, viewFireNotification }
enum FireMapStatus {
view,
subscriptionConfirm,
unsubscribe,
edit,
viewFireNotification
}
enum FireMapLayer { osmcGrey, esriSatellite, osmc, esri, esriTerrain }
@immutable
@ -18,9 +24,9 @@ class FireMapState {
const FireMapState.initial()
: this.status = FireMapStatus.view,
this.layer = FireMapLayer.osmcGrey,
this.layer = FireMapLayer.osmcGrey,
this.yourLocation = null,
this.fireNotification = null,
this.fireNotification = null,
this.numFires = 0,
this.fires = const [],
this.falsePos = const [],
@ -28,11 +34,11 @@ class FireMapState {
FireMapState(
{this.status: FireMapStatus.view,
this.layer: FireMapLayer.osmcGrey,
this.layer: FireMapLayer.osmcGrey,
this.yourLocation,
this.numFires,
this.fires,
this.fireNotification,
this.fires,
this.fireNotification,
this.falsePos,
this.industries});
@ -63,9 +69,9 @@ class FireMapState {
other is FireMapState &&
runtimeType == other.runtimeType &&
status == other.status &&
layer == other.layer &&
layer == other.layer &&
numFires == other.numFires &&
fireNotification == other.fireNotification &&
fireNotification == other.fireNotification &&
fires == other.fires &&
falsePos == other.falsePos &&
industries == other.industries &&

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter/material.dart';
import 'falsePositiveTypes.dart';
import 'package:bson_objectid/bson_objectid.dart';
import 'package:fires_flutter/models/yourLocation.dart';
@ -18,8 +19,8 @@ class FiresApi {
resty.globalClient = new ht.IOClient();
}
Future<String> createUser(
AppState state, String mobileToken, String lang) async {
Future<String> createUser(AppState state, String mobileToken,
String lang) async {
assert(state.firesApiUrl != null);
assert(state.firesApiKey != null);
assert(mobileToken != null);
@ -50,18 +51,18 @@ class FiresApi {
if (response.statusCode == 200) {
// print(response.body);
final dataSubscriptions =
json.decode(response.body)['data']['subscriptions'];
json.decode(response.body)['data']['subscriptions'];
List<YourLocation> subscribed = [];
for (int i = 0; i < dataSubscriptions.length; i++) {
var el = dataSubscriptions[i];
var lat = el['location']['lat'];
var lon = el['location']['lon'];
subscribed.add(new YourLocation(
id: ObjectId.fromHexString(el['_id']['_str']),
lat: lat,
lon: lon,
subscribed: true,
distance: el['distance']));
id: ObjectId.fromHexString(el['_id']['_str']),
lat: lat,
lon: lon,
subscribed: true,
distance: el['distance']));
}
return subscribed;
@ -116,12 +117,13 @@ class FiresApi {
});
}
Future<UpdateYourLocationMapStatsAction> getFiresInLocation(
{AppState state, double lat, double lon, int distance}) async {
Future<UpdateFireMapStatsAction> getFiresInLocation(
{AppState state, double lat, double lon, int distance}) async {
assert(state.firesApiUrl != null);
assert(state.firesApiKey != null);
var url = '${state.firesApiUrl}fires-in-full/${state
.firesApiKey}/${lat}/${lon}/${distance}';
if (globals.isDevelopment) print(url);
return await resty.get(url).go().then((response) {
if (response.statusCode == 200) {
var resultDecoded = json.decode(response.body);
@ -135,13 +137,13 @@ class FiresApi {
var industriesCount = industries.length;
var falsePosCount = falsePos.length;
print(
'real: $numFires, fire: $firesCount falsePos: $falsePosCount industries: $industriesCount');
'(Pos: $lat, $lon) real: $numFires, fire: $firesCount falsePos: $falsePosCount industries: $industriesCount');
}
return new UpdateYourLocationMapStatsAction(
numFires: numFires,
fires: fires,
falsePos: falsePos,
industries: industries);
return new UpdateFireMapStatsAction(
numFires: numFires,
fires: fires,
falsePos: falsePos,
industries: industries);
} else
throw Exception('Wrong response trying to get fire data');
});
@ -159,15 +161,16 @@ class FiresApi {
// print(resultDecoded['data']['union']);
List<Polyline> union = [];
final multipolygon =
json.decode(resultDecoded['data']['union']['value'])['geometry']
['coordinates'];
json.decode(resultDecoded['data']['union']['value'])['geometry']
['coordinates'];
for (List<dynamic> polygon in multipolygon) {
for (List<dynamic> hole in polygon) {
List<LatLng> points = [];
for (List<dynamic> point in hole) {
points.add(new LatLng(point[1].toDouble(), point[0].toDouble()));
}
union.add(new Polyline(points: points, color: color, strokeWidth: 3.0));
union.add(
new Polyline(points: points, color: color, strokeWidth: 3.0));
}
}
return union;
@ -175,4 +178,32 @@ class FiresApi {
throw Exception('Wrong response trying to get fire data');
});
}
Future<bool> markFalsePositive(AppState state, String mobileToken,
String sealed, FalsePositiveType type) async {
assert(state.firesApiUrl != null);
assert(state.firesApiKey != null);
assert(mobileToken != null);
assert(sealed != null);
assert(type != null);
final params = {
"token": state.firesApiKey,
"mobileToken": mobileToken,
"sealed": sealed,
"type": type.toString()
};
final String url = '${state.firesApiUrl}mobile/falsepositive';
return await resty.post(url).json(params).go().then((response) {
if (response.statusCode == 200) {
// print(response.body);
if (globals.isDevelopment) print(
json.decode(response.body)['data']['upsert']);
return true;
} else {
debugPrint(json.decode(response.body));
return false;
}
});
}
}

View file

@ -97,13 +97,7 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
}
if (action is ShowFireNotificationMapAction) {
api
.getFiresInLocation(
state: store.state,
lat: action.notif.lat,
lon: action.notif.lon,
distance: 1) // FalsePositive/server/publications.js
.then((result) => store.dispatch(result));
getFiresStatsInFire(store, action.notif);
}
if (action is UpdateYourLocationAction) {
@ -221,10 +215,30 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
});
}
if (action is MarkFireAsFalsePositiveAction) {
api.markFalsePositive(store.state, store.state.user.token, action.notif.sealed, action.type).then((result) {
if (result) {
// Not necessary
// store.dispatch(new UpdatedFireNotificationAction(action.notif));
getFiresStatsInFire(store, action.notif);
}
});
}
// Make sure our actions continue on to the reducer.
next(action);
}
void getFiresStatsInFire(Store<AppState> store, FireNotification notif) {
api
.getFiresInLocation(
state: store.state,
lat: notif.lat,
lon: notif.lon,
distance: 1) // FalsePositive/server/publications.js
.then((result) => store.dispatch(result));
}
void unsubsViaApi(Store<AppState> store, ObjectId id, onUnsubs) {
api.unsubscribe(store.state, id.toHexString()).then((res) {
onUnsubs();

View file

@ -12,13 +12,13 @@ class UpdateYourLocationMapAction extends FiresMapActions {
);
}
class UpdateYourLocationMapStatsAction extends FiresMapActions {
class UpdateFireMapStatsAction extends FiresMapActions {
int numFires;
List<dynamic> fires = [];
List<dynamic> falsePos = [];
List<dynamic> industries = [];
UpdateYourLocationMapStatsAction(
UpdateFireMapStatsAction(
{@required this.numFires,
@required this.fires,
@required this.falsePos,

View file

@ -8,7 +8,7 @@ final fireMapReducer = combineReducers<FireMapState>([
_showYourLocationMap),
new TypedReducer<FireMapState, ShowFireNotificationMapAction>(
_showFireNotificationMap),
new TypedReducer<FireMapState, UpdateYourLocationMapStatsAction>(
new TypedReducer<FireMapState, UpdateFireMapStatsAction>(
_updateYourLocationMapStats),
new TypedReducer<FireMapState, SubscribeAction>(_subscribeYourLocationMap),
new TypedReducer<FireMapState, SubscribeConfirmAction>(
@ -27,7 +27,7 @@ final fireMapReducer = combineReducers<FireMapState>([
]);
FireMapState _updateYourLocationMapStats(
FireMapState state, UpdateYourLocationMapStatsAction action) {
FireMapState state, UpdateFireMapStatsAction action) {
return state.copyWith(
numFires: action.numFires,
fires: action.fires,

View file

@ -1,4 +1,5 @@
import 'package:fires_flutter/models/fireNotification.dart';
import '../models/falsePositiveTypes.dart';
abstract class FireNotificationActions {}
@ -45,3 +46,16 @@ class ReadedFireNotificationAction extends FireNotificationActions {
ReadedFireNotificationAction(this.notif);
}
class MarkFireAsFalsePositiveAction extends FireNotificationActions {
final FireNotification notif;
final FalsePositiveType type;
MarkFireAsFalsePositiveAction(this.notif, this.type);
}
class UpdatedFireNotificationAction extends FireNotificationActions {
final FireNotification notif;
UpdatedFireNotificationAction(this.notif);
}

View file

@ -1,17 +1,19 @@
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:redux/redux.dart';
import 'actions.dart';
import 'package:fires_flutter/models/fireNotification.dart';
final fireNotificationReducer = combineReducers<List<FireNotification>>([
new TypedReducer<List<FireNotification>, AddedFireNotificationAction>(
_addedFireNotification),
new TypedReducer<List<FireNotification>, DeletedFireNotificationAction>(
_deletedFireNotification),
_deletedFireNotification),
new TypedReducer<List<FireNotification>, UpdatedFireNotificationAction>(
_updatedFireNotification),
new TypedReducer<List<FireNotification>, DeletedAllFireNotificationAction>(
_deletedAllFireNotifications),
_deletedAllFireNotifications),
new TypedReducer<List<FireNotification>, ReadedFireNotificationAction>(
_readedFireNotification)
_readedFireNotification)
]);
List<FireNotification> _addedFireNotification(
@ -20,10 +22,18 @@ List<FireNotification> _addedFireNotification(
}
List<FireNotification> _deletedFireNotification(
List<FireNotification> notifications, DeletedFireNotificationAction action) {
List<FireNotification> notifications,
DeletedFireNotificationAction action) {
return new List.from(notifications)..remove(action.notif);
}
List<FireNotification> _updatedFireNotification(
List<FireNotification> notifications, UpdatedFireNotificationAction action) {
return notifications
.map((notif) => notif.id == action.notif.id ? action.notif : notif)
.toList();
}
List<FireNotification> _readedFireNotification(
List<FireNotification> notifications, ReadedFireNotificationAction action) {
return notifications
@ -33,6 +43,7 @@ List<FireNotification> _readedFireNotification(
}
List<FireNotification> _deletedAllFireNotifications(
List<FireNotification> notifications, DeletedAllFireNotificationAction action) {
List<FireNotification> notifications,
DeletedAllFireNotificationAction action) {
return new List<FireNotification>();
}