#11 Generate place for challenge

Birleştirildi
iwa-13 iwa-13/main 3 yıl önce içindeki iwa-13/marcin-local işlemelerini 12 ile birleştirdi

+ 31 - 0
app/lib/challenges/challenge.dart

@@ -0,0 +1,31 @@
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:flutter/material.dart';
+import 'package:latlong2/latlong.dart';
+
+import '../navigation/navigation_page.dart';
+
+Widget buildChallenge(DocumentSnapshot<Map<String, dynamic>> data, BuildContext context) {
+  final LatLng coordinates =
+      LatLng(data.get('location')['geopoint'].latitude, data.get('location')['geopoint'].longitude);
+  return Center(
+    child: Column(
+      mainAxisAlignment: MainAxisAlignment.center,
+      children: [
+        const Text('Your challenge for this week:'),
+        Text(data.get('name')),
+        TextButton(
+            onPressed: () {
+              Navigator.push(
+                context,
+                MaterialPageRoute(
+                  builder: (context) => NavigationPage(
+                    destination: coordinates,
+                  ),
+                ),
+              );
+            },
+            child: const Text('Navigate me'))
+      ],
+    ),
+  );
+}

+ 35 - 0
app/lib/challenges/challenge_page.dart

@@ -0,0 +1,35 @@
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:flutter/material.dart';
+import 'package:physigo/challenges/challenges_utils.dart';
+import 'package:physigo/challenges/challenge.dart';
+
+class ChallengePage extends StatelessWidget {
+  const ChallengePage({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      body: FutureBuilder<DocumentSnapshot<Map<String, dynamic>>>(
+        future: ChallengesUtils.getChallenge(),
+        builder: (context, snapshot) {
+          switch (snapshot.connectionState) {
+            case ConnectionState.waiting:
+              return const Center(
+                child: Text('Getting challenges...'),
+              );
+            case ConnectionState.done:
+              if (snapshot.hasError) {
+                return const Text('Error occurred');
+              }
+              if (snapshot.hasData) {
+                return buildChallenge(snapshot.data!, context);
+              }
+              return const Text('No data');
+            default:
+              return const Text('Dunno');
+          }
+        },
+      ),
+    );
+  }
+}

+ 67 - 0
app/lib/challenges/challenges_utils.dart

@@ -0,0 +1,67 @@
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:geoflutterfire/geoflutterfire.dart';
+import 'dart:math';
+
+class ChallengesUtils {
+  static final geo = Geoflutterfire();
+  static final _firestore = FirebaseFirestore.instance;
+
+  static Future<DocumentSnapshot<Map<String, dynamic>>> getChallenge() async {
+    Future<DocumentSnapshot<Map<String, dynamic>>> challenge;
+    if (await shouldGenerateChallenge()) {
+      challenge = generateChallenge();
+    } else {
+      final user = _firestore.collection('Users').doc('tlmysIvwTBaoZKWqBofx');
+      challenge = user.get().then((value) => value.get('weekly_place').get());
+    }
+    return challenge;
+  }
+
+  static Future<bool> shouldGenerateChallenge() async {
+    final user = _firestore.collection('Users').doc('tlmysIvwTBaoZKWqBofx');
+    DateTime now = DateTime.now();
+    DateTime today = DateTime(now.year, now.month, now.day);
+    DateTime lastMonday = today.subtract(Duration(days: today.weekday - 1));
+    int secondsSinceEpoch = lastMonday.millisecondsSinceEpoch ~/ 1000;
+    bool shouldGenerate = await user.get().then((value) {
+      final dif = secondsSinceEpoch - value.get('last_challenge_date').seconds;
+      return dif / 3600 / 24 > 7;
+    });
+    return Future.value(shouldGenerate);
+  }
+
+  static Future<DocumentSnapshot<Map<String, dynamic>>> generateChallenge() async {
+    final user = await _firestore.collection('Users').doc('tlmysIvwTBaoZKWqBofx').get();
+    Query<Map<String, dynamic>> places = _firestore.collection('Places');
+    double innerRadius = 3;
+    double outerRadius = 5;
+    String randomPlaceID = '';
+
+    Stream<List<DocumentSnapshot>> possiblePlaces = geo.collection(collectionRef: places).within(
+        center: geo.point(latitude: user.get('address').latitude, longitude: user.get('address').longitude),
+        radius: outerRadius,
+        field: 'location',
+        strictMode: true);
+
+    Stream<List<DocumentSnapshot>> placesTooClose = geo.collection(collectionRef: places).within(
+        center: geo.point(latitude: user.get('address').latitude, longitude: user.get('address').longitude),
+        radius: innerRadius,
+        field: 'location',
+        strictMode: true);
+
+    List<DocumentSnapshot<Object?>> possiblePlacesList = await possiblePlaces.first;
+    List<DocumentSnapshot<Object?>> placesTooCloseList = await placesTooClose.first;
+    final ids = <String>{};
+    for (DocumentSnapshot<Object?> element in placesTooCloseList) {
+      ids.add(element.id);
+    }
+    possiblePlacesList.retainWhere((element) => !ids.contains(element.id));
+    int numOfPlaces = possiblePlacesList.length;
+    randomPlaceID = possiblePlacesList[Random().nextInt(numOfPlaces)].id;
+    _firestore.collection('Users').doc('tlmysIvwTBaoZKWqBofx').update({
+      'weekly_place': _firestore.collection('Places').doc(randomPlaceID),
+      'last_challenge_date': Timestamp.now(),
+    });
+    return _firestore.collection('Places').doc(randomPlaceID).get();
+  }
+}

+ 7 - 0
app/pubspec.lock

@@ -205,6 +205,13 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  geoflutterfire:
+    dependency: "direct main"
+    description:
+      name: geoflutterfire
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.3"
   geolocator:
     dependency: "direct main"
     description:

+ 1 - 0
app/pubspec.yaml

@@ -49,6 +49,7 @@ dependencies:
   flutter_activity_recognition: ^1.3.0
   geolocator: ^8.2.1
   provider: ^6.0.1
+  geoflutterfire: ^3.0.3
 
 dev_dependencies:
   flutter_test:

+ 11 - 5
scripts/fetch_places.py

@@ -1,5 +1,6 @@
 import requests
 import json
+import pygeohash as pgh
 from google.cloud import firestore
 
 import os
@@ -15,24 +16,29 @@ with open('map features.txt', 'r') as f:
     for line in f:
         if line[0] == "#":
             continue
+        category, category_name = line.split(":")
+        category_name = category_name.replace("\"", "").strip()
         overpass_query = f"""
 [out:json];
 area["ISO3166-1"="PL"][admin_level=2];
-node[{line[:-1]}](area);
+node[{category}](area);
 out center;
 """
         response = requests.get(overpass_url, params={"data": overpass_query})
         try:
             elements = response.json()["elements"]
         except json.JSONDecodeError:
-            print(f"Could not decode data for {line[:-1]}")
+            print(f"Could not decode data for {category}")
             continue
-        object_type = line.split("=")[1].strip()
+        object_type = category.split("=")[1].strip()
         data = {}
         for el in elements:
             try:
-                data["name"] = el["tags"].get("name", object_type)
-                data["location"] = firestore.GeoPoint(el["lat"], el["lon"])
+                data["name"] = el["tags"].get("name", category_name)
+                data["location"] = {
+                    "geohash": pgh.encode(el["lat"], el["lon"], precision=9),
+                    "geopoint": firestore.GeoPoint(el["lat"], el["lon"])
+                }
                 data["category"] = object_type
                 batch.set(db.collection("Places").document(str(el["id"])), data)
                 count += 1

+ 13 - 13
scripts/map features.txt

@@ -3,17 +3,17 @@
 #amenity=college
 #amenity=library
 #amenity=university
-#amenity=arts_centre
-#amenity=community_centre
-#amenity=fountain
+amenity=arts_centre:"Arts centre"
+amenity=community_centre:"Community centre"
+amenity=fountain:"Fountain"
 #amenity=planetarium
 #amenity=public_bookcase
-#amenity=social_centre
+amenity=social_centre:"Social centre"
 #amenity=theatre
 #amenity=townhall
 #amenity=bbq
-#amenity=clock
-#boundary=forest
+amenity=clock:"Clock"
+boundary=forest:"Forest"
 #building=cathedral
 #building=chapel
 #building=church
@@ -30,13 +30,13 @@
 #historic=memorial
 #historic=monument
 #historic=ruins
-#leisure=dog_park
-#leisure=park
-#tourism=artwork
-#tourism=attraction
+leisure=dog_park:"Dog park"
+leisure=park:"Park"
+tourism=artwork:"Artwork"
+tourism=attraction:"Attraction"
 #tourism=gallery
 #tourism=museum
-#tourism=picnic_site
+tourism=picnic_site:"Picnic site"
 #tourism=theme_park
-#tourism=view_point
-#tourism=zoo
+tourism=view_point :View point"
+tourism=zoo:"Zoo"